Files
Federico Cerutti 247a0ffd55 Faster ISR sharing in ASM
While in the process of writing a new codec I stumbled upon the long
standing issue of ISR sharing in AVR MCUs.
Actually this is accomplished with an ISR written in C, which simply
calls another plain C function referenced via a function pointer
updated at runtime.
This approach is slow because GCC can't optimize the ISR, since it
does not know which registers will be used, so it defaults to push/pop
all of them.

My solution keeps the concept of pointers to functions, but greatly
improves speed by reducing the ISR itself to the bare minimum to call
another function, which will be compiled by GCC as a signal.
This means all interrupt optimizations will be put in place by GCC,
while ISRs can be still written in plain C code, but the overhead is
now much smaller. It has proven to reduce by 20 the number of
instructions for every ISR, mainly pushes/pops, which cuts the clock
cycle count down by 30 cycles (1 cycle for every push, 2 for pop).
In time units, this means 1.1 uSec are saved for every ISR invocation
and every shared ISR now takes only 13 clock cycles more than the
"bare" one.

A new ISR_SHARED function type has been defined in Common.h to hide
away from the programmer GCC attributes. All new shared interrupt
handling routines should be defined of this type to prevent
stack and registers corruption.

Minor changes were made to Codec.h to allow including it in .S files.
2019-12-08 14:11:05 +01:00

396 lines
13 KiB
C

/*
* Reader14443-2A.c
*
* Created on: 26.08.2014
* Author: sk
*/
#include "Reader14443-2A.h"
#include "Codec.h"
#include "../System.h"
#include "../Application/Application.h"
#include "LEDHook.h"
#include "Terminal/Terminal.h"
#include <util/delay.h>
#define SAMPLE_RATE_SYSTEM_CYCLES ((uint16_t) (((uint64_t) F_CPU * ISO14443A_BIT_RATE_CYCLES) / CODEC_CARRIER_FREQ) )
#define ISO14443A_RX_MINIMUM_BITCOUNT 4
#define ISO14443A_PICC_TO_PCD_FDT_PRESCALER TC_CLKSEL_DIV8_gc // please change ISO14443A_PICC_TO_PCD_MIN_FDT when changing this
#define ISO14443A_PICC_TO_PCD_MIN_FDT 293
static volatile struct {
volatile bool Start;
volatile bool RxDone;
volatile bool RxPending;
} Flags = { 0 };
static volatile uint16_t RxPendingSince;
static volatile enum {
STATE_IDLE,
STATE_MILLER_SEND,
STATE_MILLER_EOF,
STATE_FDT
} State;
// GPIOR0 and 1 are used as storage for the timer value of the current modulation
#define LastBit Codec8Reg2 // GPIOR2
// GPIOR3 is used for some internal flags
#define BitCount CodecCount16Register1 // GPIOR5:4
#define SampleRegister GPIOR6
#define BitCountUp GPIOR7
#define CodecBufferIdx GPIOR8
#define CodecBufferPtr CodecPtrRegister2
#define UINT8DIFF(a,b) ((uint8_t) (a-b))
void Reader14443ACodecInit(void) {
/* Initialize common peripherals and start listening
* for incoming data. */
CodecInitCommon();
isr_func_TCD0_CCC_vect = &isr_Reader14443_2A_TCD0_CCC_vect;
CodecSetDemodPower(true);
CODEC_TIMER_SAMPLING.PER = SAMPLE_RATE_SYSTEM_CYCLES - 1;
CODEC_TIMER_SAMPLING.CCB = 0;
CODEC_TIMER_SAMPLING.CCC = 0;
CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_OFF_gc;
CODEC_TIMER_SAMPLING.INTCTRLA = 0;
CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCBINTLVL_OFF_gc;
CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_DIV1_gc;
CODEC_TIMER_LOADMOD.CTRLA = 0;
State = STATE_IDLE;
Flags.Start = false;
Flags.RxPending = false;
Flags.RxDone = false;
}
void Reader14443ACodecDeInit(void) {
CodecSetDemodPower(false);
CodecReaderFieldStop();
CODEC_TIMER_SAMPLING.CTRLA = 0;
CODEC_TIMER_SAMPLING.INTCTRLB = 0;
CODEC_TIMER_LOADMOD.CTRLA = 0;
CODEC_TIMER_LOADMOD.INTCTRLB = 0;
Flags.RxDone = false;
Flags.RxPending = false;
Flags.Start = false;
}
INLINE void Insert0(void) {
SampleRegister >>= 1;
if (++BitCount % 8)
return;
*CodecBufferPtr++ = SampleRegister;
}
INLINE void Insert1(void) {
SampleRegister = (SampleRegister >> 1) | 0x80;
if (++BitCount % 8)
return;
*CodecBufferPtr++ = SampleRegister;
}
// End of Card-> reader communication and enter frame delay time
INLINE void Reader14443A_EOC(void) {
CODEC_TIMER_LOADMOD.INTCTRLB = 0;
CODEC_TIMER_LOADMOD.CTRLA = TC_CLKSEL_OFF_gc;
CODEC_TIMER_TIMESTAMPS.INTCTRLB = 0;
CODEC_TIMER_TIMESTAMPS.CTRLA = TC_CLKSEL_OFF_gc;
ACA.AC1CTRL &= ~AC_ENABLE_bm;
if (BitCount & 1) {
if (SampleRegister & 0x80)
Insert0();
else
Insert1();
}
if (BitCount % 8) // copy the last byte, if there is an incomplete byte
CodecBuffer[BitCount / 8] = SampleRegister >> (8 - (BitCount % 8));
Flags.RxDone = true;
Flags.RxPending = false;
// set up timer that forces the minimum frame delay time from PICC to PCD
CODEC_TIMER_LOADMOD.PER = 0xFFFF;
CODEC_TIMER_LOADMOD.CNT = 0;
CODEC_TIMER_LOADMOD.INTCTRLA = 0;
CODEC_TIMER_LOADMOD.INTCTRLB = 0;
CODEC_TIMER_LOADMOD.CTRLD = 0;
CODEC_TIMER_LOADMOD.CTRLA = ISO14443A_PICC_TO_PCD_FDT_PRESCALER;
State = STATE_FDT;
}
INLINE void BufferToSequence(void) {
uint16_t count = BitCount;
if (count > BITS_PER_BYTE * CODEC_BUFFER_SIZE / 2) // todo is this correct?
return;
BitCount = 0;
memcpy(CodecBuffer + CODEC_BUFFER_SIZE / 2, CodecBuffer, (count + 7) / 8);
uint8_t *Buffer = CodecBuffer + CODEC_BUFFER_SIZE / 2;
CodecBufferPtr = CodecBuffer;
// Modified Miller Coding ISO14443-2 8.1.3
Insert1(); // SOC
Insert0();
uint16_t i;
uint8_t last = 0;
for (i = 1; i <= count; i++) {
if ((*Buffer) & 1) {
Insert0();
Insert1();
last = 1;
} else {
if (last) {
Insert0();
Insert0();
} else {
Insert1();
Insert0();
}
last = 0;
}
*Buffer >>= 1;
if ((i % 8) == 0)
Buffer++;
}
if (last == 0) { // EOC
Insert1();
Insert0();
}
if (BitCount % 8)
CodecBuffer[BitCount / 8] = SampleRegister >> (8 - (BitCount % 8));
}
// ISR (TCD0_CCC_vect)
// Frame Delay Time PCD to PICC ends
ISR_SHARED isr_Reader14443_2A_TCD0_CCC_vect(void) {
CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCCIF_bm;
CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCCINTLVL_OFF_gc;
/* Enable the AC interrupt, which either finds the SOC and then starts the pause-finding timer,
* or it is triggered before the SOC, which mostly isn't bad at all, since the first pause
* needs to be found. */
ACA.STATUS = AC_AC1IF_bm;
ACA.AC1CTRL = AC_HSMODE_bm | AC_HYSMODE_NO_gc | AC_INTMODE_FALLING_gc | AC_INTLVL_HI_gc | AC_ENABLE_bm;
CodecBufferPtr = CodecBuffer; // use GPIOR for faster access
BitCount = 1; // FALSCH todo the first modulation of the SOC is "found" implicitly
SampleRegister = 0x00;
RxPendingSince = SystemGetSysTick();
Flags.RxPending = true;
// reset for future use
CodecBufferIdx = 0;
BitCountUp = 0;
State = STATE_IDLE;
PORTE.OUTTGL = PIN3_bm;
}
// Reader -> card send bits finished
// Start Frame delay time PCD to PICC
void Reader14443AMillerEOC(void) {
CODEC_TIMER_SAMPLING.PER = 5 * SAMPLE_RATE_SYSTEM_CYCLES - 1;
CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCBIF_bm | TC0_CCCIF_bm;
CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCBINTLVL_OFF_gc | TC_CCCINTLVL_HI_gc;
CODEC_TIMER_SAMPLING.PERBUF = SAMPLE_RATE_SYSTEM_CYCLES - 1;
PORTE.OUTTGL = PIN3_bm;
}
// EOC of Card->Reader found
ISR(CODEC_TIMER_TIMESTAMPS_CCA_VECT) { // EOC found
Reader14443A_EOC();
}
// This interrupt find Card -> Reader SOC
ISR(ACA_AC1_vect) { // this interrupt either finds the SOC or gets triggered before
ACA.AC1CTRL &= ~AC_INTLVL_HI_gc; // disable this interrupt
// enable the pause-finding timer
CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_RESTART_gc | TC_EVSEL_CH0_gc;
CODEC_TIMER_LOADMOD.CTRLA = TC_CLKSEL_DIV1_gc;
}
// Decode the Card -> Reader signal
// according to the pause and modulated period
// if the half bit duration is modulated, then add 1 to buffer
// if the half bit duration is not modulated, then add 0 to buffer
ISR(CODEC_TIMER_LOADMOD_CCA_VECT) { // pause found
uint8_t tmp = CODEC_TIMER_TIMESTAMPS.CNTL;
CODEC_TIMER_TIMESTAMPS.CNT = 0;
/* This needs to be done only on the first call,
* but doing this only on a condition means wasting time, so we do it every time. */
CODEC_TIMER_TIMESTAMPS.CTRLA = TC_CLKSEL_DIV4_gc;
switch (tmp) { // decide how many half bit periods have been modulations
case 0 ... 48: // 32 ticks is one half of a bit period
return;
case 49 ... 80: // 64 ticks are a full bit period
Insert1();
Insert0();
return;
case 81 ... 112: // 96 ticks are 3 half bit periods
if (BitCount & 1) {
Insert1();
Insert1();
Insert0();
} else {
Insert1();
Insert0();
Insert0();
}
return;
default: // every value over 96 + 16 (tolerance) is considered to be 4 half bit periods
Insert1();
Insert1();
Insert0();
Insert0();
return;
}
return;
}
void Reader14443ACodecTask(void) {
if (Flags.RxPending && SYSTICK_DIFF(RxPendingSince) > Reader_FWT + 1) {
Reader14443A_EOC();
BitCount = 0;
Flags.RxDone = true;
Flags.RxPending = false;
}
if (CodecIsReaderToBeRestarted() || !CodecIsReaderFieldReady())
return;
if (!Flags.RxPending && (Flags.Start || Flags.RxDone)) {
if (State == STATE_FDT && CODEC_TIMER_LOADMOD.CNT < ISO14443A_PICC_TO_PCD_MIN_FDT) // we are in frame delay time, so we can return later
return;
if (Flags.RxDone && BitCount > 0) { // decode the raw received data
if (BitCount < ISO14443A_RX_MINIMUM_BITCOUNT * 2) {
BitCount = 0;
} else {
uint8_t TmpCodecBuffer[CODEC_BUFFER_SIZE];
memcpy(TmpCodecBuffer, CodecBuffer, (BitCount + 7) / 8);
CodecBufferPtr = CodecBuffer;
uint16_t BitCountTmp = 2, TotalBitCount = BitCount;
BitCount = 0;
bool breakflag = false;
TmpCodecBuffer[0] >>= 2; // with this (and BitCountTmp = 2), the SOC is ignored
// Manchester Code ISO14443-2 8.2.5
while (!breakflag && BitCountTmp < TotalBitCount) {
uint8_t Bit = TmpCodecBuffer[BitCountTmp / 8] & 0x03;
TmpCodecBuffer[BitCountTmp / 8] >>= 2;
switch (Bit) {
case 0b10:
Insert1();
break;
case 0b01:
Insert0();
break;
case 0b00: // EOC
breakflag = true;
break;
default:
// error, should not happen, TODO handle this
break;
}
BitCountTmp += 2;
}
if (BitCount % 8) // copy the last byte, if there is an incomplete byte
CodecBuffer[BitCount / 8] = SampleRegister >> (8 - (BitCount % 8));
LEDHook(LED_CODEC_RX, LED_PULSE);
LogEntry(LOG_INFO_CODEC_RX_DATA_W_PARITY, CodecBuffer, (BitCount + 7) / 8);
}
}
Flags.Start = false;
Flags.RxDone = false;
/* Call application with received data */
BitCount = ApplicationProcess(CodecBuffer, BitCount);
if (BitCount > 0) {
/*
* Prepare for Manchester decoding.
* The basic idea is to use two timers. The first one will be reset everytime the DEMOD signal
* passes a (configurable) threshold. This is realized with the event system and an analog
* comparator.
* Once this timer reaches 3/4 of a bit half (this means it has not been reset this long), we
* assume there is a pause. Now we read the second timers count value and can decide how many
* bit halves had modulations since the last pause.
*/
/* Configure and enable the analog comparator for finding pauses in the DEMOD signal. */
ACA.AC1CTRL = AC_HSMODE_bm | AC_HYSMODE_NO_gc | AC_INTMODE_FALLING_gc | AC_ENABLE_bm;
/* This timer will be used to detect the pauses between the modulation sequences. */
CODEC_TIMER_LOADMOD.CTRLA = 0;
CODEC_TIMER_LOADMOD.CNT = 0;
CODEC_TIMER_LOADMOD.PER = 0xFFFF; // with 27.12 MHz this is exactly one half bit width
CODEC_TIMER_LOADMOD.CCA = 95; // with 27.12 MHz this is 3/4 of a half bit width
CODEC_TIMER_LOADMOD.INTCTRLA = 0;
CODEC_TIMER_LOADMOD.INTFLAGS = TC1_CCAIF_bm;
CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCAINTLVL_HI_gc;
/* This timer will be used to find out how many bit halfs since the last pause have been passed. */
CODEC_TIMER_TIMESTAMPS.CNT = 0;
CODEC_TIMER_TIMESTAMPS.PER = 0xFFFF;
CODEC_TIMER_TIMESTAMPS.CCA = 160;
CODEC_TIMER_TIMESTAMPS.INTCTRLA = 0;
CODEC_TIMER_TIMESTAMPS.INTFLAGS = TC1_CCAIF_bm;
CODEC_TIMER_TIMESTAMPS.INTCTRLB = TC_CCAINTLVL_LO_gc;
/* Use the event system for resetting the pause-detecting timer. */
EVSYS.CH0MUX = EVSYS_CHMUX_ACA_CH1_gc; // on every ACA_AC1 INT
EVSYS.CH0CTRL = EVSYS_DIGFILT_1SAMPLE_gc;
ACA.AC0CTRL = 0;
CODEC_DEMOD_IN_PORT.INTCTRL = 0;
LEDHook(LED_CODEC_TX, LED_PULSE);
LogEntry(LOG_INFO_CODEC_TX_DATA_W_PARITY, CodecBuffer, (BitCount + 7) / 8);
/* Set state and start timer for Miller encoding. */
// Send bits to card using TCD0_CCB interrupt (See Reader14443-ISR.S)
BufferToSequence();
State = STATE_MILLER_SEND;
CodecBufferPtr = CodecBuffer;
CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCBIF_bm;
CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCBINTLVL_HI_gc;
_delay_loop_1(85);
}
}
}
void Reader14443ACodecStart(void) {
/* Application wants us to start a card transaction */
BitCount = 0;
Flags.Start = true;
CodecReaderFieldStart();
}
void Reader14443ACodecReset(void) {
Reader14443A_EOC(); // this breaks every interrupt etc.
State = STATE_IDLE;
Flags.RxDone = false;
Flags.Start = false;
CodecReaderFieldStop();
}