/* * ISO14443A.c * * Created on: 18.02.2013 * Author: skuser */ #include "ISO14443-2A.h" #include "../System.h" #include "../Application/Application.h" #include "../LEDHook.h" #include "Codec.h" #include "Log.h" /* Sampling is done using internal clock, synchronized to the field modulation. * For that we need to convert the bit rate for the internal clock. */ #define SAMPLE_RATE_SYSTEM_CYCLES ((uint16_t) (((uint64_t) F_CPU * ISO14443A_BIT_RATE_CYCLES) / CODEC_CARRIER_FREQ) ) #define ISO14443A_MIN_BITS_PER_FRAME 7 static volatile struct { volatile bool DemodFinished; volatile bool LoadmodFinished; } Flags = { 0 }; typedef enum { /* Demod */ DEMOD_DATA_BIT, DEMOD_PARITY_BIT, /* Loadmod */ LOADMOD_FDT, LOADMOD_START, LOADMOD_START_BIT0, LOADMOD_START_BIT1, LOADMOD_DATA0, LOADMOD_DATA1, LOADMOD_PARITY0, LOADMOD_PARITY1, LOADMOD_STOP_BIT0, LOADMOD_STOP_BIT1, LOADMOD_FINISHED } StateType; /* Define pseudo variables to use fast register access. This is useful for global vars */ #define DataRegister Codec8Reg0 #define StateRegister Codec8Reg1 #define ParityRegister Codec8Reg2 #define SampleIdxRegister Codec8Reg2 #define SampleRegister Codec8Reg3 #define BitSent CodecCount16Register1 #define BitCount CodecCount16Register2 #define CodecBufferPtr CodecPtrRegister1 #define ParityBufferPtr CodecPtrRegister2 static void StartDemod(void) { /* Activate Power for demodulator */ CodecSetDemodPower(true); CodecBufferPtr = CodecBuffer; ParityBufferPtr = &CodecBuffer[ISO14443A_BUFFER_PARITY_OFFSET]; DataRegister = 0; SampleRegister = 0; SampleIdxRegister = 0; BitCount = 0; StateRegister = DEMOD_DATA_BIT; /* Configure sampling-timer free running and sync to first modulation-pause. */ CODEC_TIMER_SAMPLING.CNT = 0; CODEC_TIMER_SAMPLING.PER = SAMPLE_RATE_SYSTEM_CYCLES - 1; CODEC_TIMER_SAMPLING.CCA = 0xFFFF; /* CCA Interrupt is not active! */ CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_DIV1_gc; CODEC_TIMER_SAMPLING.CTRLD = TC_EVACT_RESTART_gc | CODEC_TIMER_MODSTART_EVSEL; CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCAIF_bm; CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCAINTLVL_HI_gc; /* Start looking out for modulation pause via interrupt. */ CODEC_DEMOD_IN_PORT.INTFLAGS = 0x03; CODEC_DEMOD_IN_PORT.INT0MASK = CODEC_DEMOD_IN_MASK0; } ISR(CODEC_DEMOD_IN_INT0_VECT) { /* This is the first edge of the first modulation-pause after StartDemod. * Now we have time to start * demodulating beginning from one bit-width after this edge. */ /* Sampling timer has been preset to sample-rate and has automatically synced * to THIS first modulation pause. Thus after exactly one bit-width from here, * an OVF is generated. We want to start sampling with the next bit and use the * XYZBUF mechanism of the xmega to automatically double the sampling rate on the * next overflow. For this we have to temporarily deactivate the automatical alignment * in order to catch next overflow event for updating the BUF registers. * We want to sample the demodulated data stream in the first quarter of the half-bit * where the pulsed miller encoded is located. */ CODEC_TIMER_SAMPLING.CTRLD = TC_EVACT_OFF_gc; CODEC_TIMER_SAMPLING.PERBUF = SAMPLE_RATE_SYSTEM_CYCLES/2 - 1; /* Half bit width */ CODEC_TIMER_SAMPLING.CCABUF = SAMPLE_RATE_SYSTEM_CYCLES/8 - 14 - 1; /* Compensate for DIGFILT and ISR prolog */ /* Setup Frame Delay Timer and wire to EVSYS. Frame delay time is * measured from last change in RF field, therefore we use * the event channel 1 (end of modulation pause) as the restart event. * The preliminary frame delay time chosen here is irrelevant, because * the correct FDT gets set automatically after demodulation. */ CODEC_TIMER_LOADMOD.CNT = 0; CODEC_TIMER_LOADMOD.PER = 0xFFFF; CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_RESTART_gc | CODEC_TIMER_MODEND_EVSEL; CODEC_TIMER_LOADMOD.INTCTRLA = TC_OVFINTLVL_OFF_gc; CODEC_TIMER_LOADMOD.INTFLAGS = TC0_OVFIF_bm; CODEC_TIMER_LOADMOD.CTRLA = CODEC_TIMER_CARRIER_CLKSEL; /* Disable this interrupt */ CODEC_DEMOD_IN_PORT.INT0MASK = 0; } ISR(CODEC_TIMER_SAMPLING_CCA_VECT) { /* This interrupt gets called twice for every bit to sample it. */ uint8_t SamplePin = CODEC_DEMOD_IN_PORT.IN & CODEC_DEMOD_IN_MASK; /* Shift sampled bit into sampling register */ SampleRegister = (SampleRegister << 1) | (!SamplePin ? 0x01 : 0x00); if (SampleIdxRegister) { SampleIdxRegister = 0; /* Analyze the sampling register after 2 samples. */ if ((SampleRegister & 0x07) == 0x07) { /* No carrier modulation for 3 sample points. EOC! */ CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_OFF_gc; CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCAIF_bm; /* By this time, the FDT timer is aligned to the last modulation * edge of the reader. So we disable the auto-synchronization and * let it count the frame delay time in the background, and generate * an interrupt once it has reached the FDT. */ CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_OFF_gc; if (SampleRegister & 0x08) { CODEC_TIMER_LOADMOD.PER = ISO14443A_FRAME_DELAY_PREV1 - 40; /* compensate for ISR prolog */ } else { CODEC_TIMER_LOADMOD.PER = ISO14443A_FRAME_DELAY_PREV0 - 40; /* compensate for ISR prolog */ } StateRegister = LOADMOD_FDT; CODEC_TIMER_LOADMOD.INTFLAGS = TC0_OVFIF_bm; CODEC_TIMER_LOADMOD.INTCTRLA = TC_OVFINTLVL_HI_gc; /* Determine if we did not receive a multiple of 8 bits. * If this is the case, right-align the remaining data and * store it into the buffer. */ uint8_t RemainingBits = BitCount % 8; if (RemainingBits != 0) { uint8_t NewDataRegister = DataRegister; while (RemainingBits++ < 8) { /* Pad with zeroes to right-align. */ NewDataRegister >>= 1; } /* TODO: Prevent buffer overflow */ *CodecBufferPtr = NewDataRegister; } /* Signal, that we have finished sampling */ Flags.DemodFinished = 1; } else { /* Otherwise, we check the two sample bits from the bit before. */ uint8_t BitSample = SampleRegister & 0xC; uint8_t Bit = 0; if (BitSample != (0x0 << 2)) { /* We have a valid bit. decode and process it. */ if (BitSample & (0x1 << 2)) { /* 01 sequence or 11 sequence -> This is a zero bit */ Bit = 0; } else { /* 10 sequence -> This is a one bit */ Bit = 1; } if (StateRegister == DEMOD_DATA_BIT) { /* This is a data bit, so shift it into the data register and * hold a local copy of it. */ uint8_t NewDataRegister = DataRegister >> 1; NewDataRegister |= (Bit ? 0x80 : 0x00); DataRegister = NewDataRegister; /* Update bitcount */ uint16_t NewBitCount = ++BitCount; if ((NewBitCount & 0x07) == 0) { /* We have reached a byte boundary! Store the data register. */ /* TODO: Prevent buffer overflow */ *CodecBufferPtr++ = NewDataRegister; /* Store bit for determining FDT at EOC and enable parity * handling on next bit. */ StateRegister = DEMOD_PARITY_BIT; } } else if (StateRegister == DEMOD_PARITY_BIT) { /* This is a parity bit. Store it */ *ParityBufferPtr++ = Bit; StateRegister = DEMOD_DATA_BIT; } else { /* Should never Happen (TM) */ } } else { /* 00 sequence. -> No valid data yet. This also occurs if we just started * sampling and have sampled less than 2 bits yet. Thus ignore. */ } } } else { /* On odd sample position just sample. */ SampleIdxRegister = ~SampleIdxRegister; } /* Make sure the sampling timer gets automatically aligned to the * modulation pauses by using the RESTART event. * This can be understood as a "poor mans PLL" and makes sure that we are * never too far out the bit-grid while sampling. */ //CODEC_TIMER_SAMPLING.CTRLD = TC_EVACT_RESTART_gc | CODEC_TIMER_MODSTART_EVSEL; } ISR(CODEC_TIMER_LOADMOD_OVF_VECT) { /* Bit rate timer. Output a half bit on the output. */ static void* JumpTable[] = { [LOADMOD_FDT] = &&LOADMOD_FDT_LABEL, [LOADMOD_START] = &&LOADMOD_START_LABEL, [LOADMOD_START_BIT0] = &&LOADMOD_START_BIT0_LABEL, [LOADMOD_START_BIT1] = &&LOADMOD_START_BIT1_LABEL, [LOADMOD_DATA0] = &&LOADMOD_DATA0_LABEL, [LOADMOD_DATA1] = &&LOADMOD_DATA1_LABEL, [LOADMOD_PARITY0] = &&LOADMOD_PARITY0_LABEL, [LOADMOD_PARITY1] = &&LOADMOD_PARITY1_LABEL, [LOADMOD_STOP_BIT0] = &&LOADMOD_STOP_BIT0_LABEL, [LOADMOD_STOP_BIT1] = &&LOADMOD_STOP_BIT1_LABEL, [LOADMOD_FINISHED] = &&LOADMOD_FINISHED_LABEL }; if ( (StateRegister >= LOADMOD_FDT) && (StateRegister <= LOADMOD_FINISHED) ) { goto *JumpTable[StateRegister]; } else { return; } LOADMOD_FDT_LABEL: /* No data has been produced, but FDT has ended. Switch over to bit-grid aligning. */ CODEC_TIMER_LOADMOD.PER = ISO14443A_BIT_GRID_CYCLES - 1; return; LOADMOD_START_LABEL: /* Application produced data. With this interrupt we are aligned to the bit-grid. */ /* Fallthrough to first bit */ LOADMOD_START_BIT0_LABEL: /* Start subcarrier generation, output startbit and align to bitrate. */ CodecSetLoadmodState(true); CodecStartSubcarrier(); CODEC_TIMER_LOADMOD.PER = ISO14443A_BIT_RATE_CYCLES / 2 - 1; StateRegister = LOADMOD_START_BIT1; return; LOADMOD_START_BIT1_LABEL: CodecSetLoadmodState(false); StateRegister = LOADMOD_DATA0; ParityRegister = ~0; BitSent = 0; /* Prefetch first byte */ DataRegister = *CodecBufferPtr; return; LOADMOD_DATA0_LABEL: if (DataRegister & 1) { CodecSetLoadmodState(true); ParityRegister = ~ParityRegister; } else { CodecSetLoadmodState(false); } StateRegister = LOADMOD_DATA1; return; LOADMOD_DATA1_LABEL: if (DataRegister & 1) { CodecSetLoadmodState(false); } else { CodecSetLoadmodState(true); } DataRegister = DataRegister >> 1; BitSent++; if ((BitSent % 8) == 0) { /* Byte boundary. Load parity bit and output it later. */ StateRegister = LOADMOD_PARITY0; } else if (BitSent == BitCount) { /* End of transmission without byte boundary. Don't send parity. */ StateRegister = LOADMOD_STOP_BIT0; } else { /* Next bit is data */ StateRegister = LOADMOD_DATA0; } return; LOADMOD_PARITY0_LABEL: if (ParityBufferPtr != NULL) { if (*ParityBufferPtr) { CodecSetLoadmodState(true); } else { CodecSetLoadmodState(false); } } else { if (ParityRegister) { CodecSetLoadmodState(true); } else { CodecSetLoadmodState(false); } } StateRegister = LOADMOD_PARITY1; return; LOADMOD_PARITY1_LABEL: if (ParityBufferPtr != NULL) { if (*ParityBufferPtr) { CodecSetLoadmodState(false); } else { CodecSetLoadmodState(true); } ParityBufferPtr++; } else { if (ParityRegister) { CodecSetLoadmodState(false); } else { CodecSetLoadmodState(true); } ParityRegister = ~0; } if (BitSent == BitCount) { /* No data left */ StateRegister = LOADMOD_STOP_BIT0; } else { /* Fetch next data and continue sending bits. */ DataRegister = *++CodecBufferPtr; StateRegister = LOADMOD_DATA0; } return; LOADMOD_STOP_BIT0_LABEL: CodecSetLoadmodState(false); StateRegister = LOADMOD_STOP_BIT1; return; LOADMOD_STOP_BIT1_LABEL: CodecSetLoadmodState(false); StateRegister = LOADMOD_FINISHED; return; LOADMOD_FINISHED_LABEL: /* We have written all of our bits. Deactivate the loadmod * timer. Also disable the bit-rate interrupt again. And * stop the subcarrier divider. */ CODEC_TIMER_LOADMOD.CTRLA = TC_CLKSEL_OFF_gc; CODEC_TIMER_LOADMOD.INTCTRLA = 0; CodecSetSubcarrier(CODEC_SUBCARRIERMOD_OFF, ISO14443A_SUBCARRIER_DIVIDER); /* Signal application that we have finished loadmod */ Flags.LoadmodFinished = 1; return; } void ISO14443ACodecInit(void) { /* Initialize some global vars and start looking out for reader commands */ Flags.DemodFinished = 0; Flags.LoadmodFinished = 0; CodecInitCommon(); StartDemod(); } void ISO14443ACodecDeInit(void) { /* Gracefully shutdown codec */ CODEC_DEMOD_IN_PORT.INT0MASK = 0; Flags.DemodFinished = 0; Flags.LoadmodFinished = 0; CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_OFF_gc; CODEC_TIMER_SAMPLING.CTRLD = TC_EVACT_OFF_gc; CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCAINTLVL_OFF_gc; CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCAIF_bm; CODEC_TIMER_LOADMOD.CTRLA = TC_CLKSEL_OFF_gc; CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_OFF_gc; CODEC_TIMER_LOADMOD.INTCTRLA = TC_OVFINTLVL_OFF_gc; CODEC_TIMER_LOADMOD.INTFLAGS = TC0_OVFIF_bm; CodecSetSubcarrier(CODEC_SUBCARRIERMOD_OFF, 0); CodecSetDemodPower(false); CodecSetLoadmodState(false); } void ISO14443ACodecTask(void) { if (Flags.DemodFinished) { Flags.DemodFinished = 0; /* Reception finished. Process the received bytes */ uint16_t DemodBitCount = BitCount; uint16_t AnswerBitCount = ISO14443A_APP_NO_RESPONSE; if (DemodBitCount >= ISO14443A_MIN_BITS_PER_FRAME) { LogEntry(LOG_INFO_CODEC_RX_DATA, CodecBuffer, (DemodBitCount+7)/8); LEDHook(LED_CODEC_RX, LED_PULSE); /* Call application if we received data */ AnswerBitCount = ApplicationProcess(CodecBuffer, DemodBitCount); if (AnswerBitCount & ISO14443A_APP_CUSTOM_PARITY) { /* Application has generated it's own parity bits. * Clear this option bit. */ AnswerBitCount &= ~ISO14443A_APP_CUSTOM_PARITY; ParityBufferPtr = &CodecBuffer[ISO14443A_BUFFER_PARITY_OFFSET]; } else { /* We have to generate the parity bits ourself */ ParityBufferPtr = 0; } } if (AnswerBitCount != ISO14443A_APP_NO_RESPONSE) { LogEntry(LOG_INFO_CODEC_TX_DATA, CodecBuffer, (AnswerBitCount + 7) / 8); LEDHook(LED_CODEC_TX, LED_PULSE); BitCount = AnswerBitCount; CodecBufferPtr = CodecBuffer; CodecSetSubcarrier(CODEC_SUBCARRIERMOD_OOK, ISO14443A_SUBCARRIER_DIVIDER); StateRegister = LOADMOD_START; } else { /* No data to be processed. Disable loadmodding and start listening again */ CODEC_TIMER_LOADMOD.CTRLA = TC_CLKSEL_OFF_gc; CODEC_TIMER_LOADMOD.INTCTRLA = 0; StartDemod(); } } if (Flags.LoadmodFinished) { Flags.LoadmodFinished = 0; /* Load modulation has been finished. Stop it and start to listen * for incoming data again. */ StartDemod(); } }