diff --git a/.github/workflows/firmware-pr.yml b/.github/workflows/firmware-pr.yml new file mode 100644 index 0000000..3241e7b --- /dev/null +++ b/.github/workflows/firmware-pr.yml @@ -0,0 +1,36 @@ +name: Firmware Build (Pull Request, Firmware Modified, Artifact in build job) + +on: + pull_request: + paths: + - "Firmware/**" + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Make a firmware build folder + run: mkdir FirmwareBuild + - name: Print Kernel Ver + run: uname -a + - name: Update APT + run: sudo apt-get update -yqq + - name: Install AVR GCC Suite + run: sudo apt-get install -yqq make autoconf build-essential ca-certificates pkg-config libreadline-dev gcc-avr binutils-avr gdb-avr avr-libc avrdude + - name: Make Firmware + run: make + working-directory: Firmware/Chameleon-Mini/ + - name: Move hex file to FirmwareBuild + run: mv Chameleon*.hex $GITHUB_WORKSPACE/FirmwareBuild/ + working-directory: Firmware/Chameleon-Mini/ + - name: Move eep file to FirmwareBuild + run: mv Chameleon*.eep $GITHUB_WORKSPACE/FirmwareBuild/ + working-directory: Firmware/Chameleon-Mini/ + - name: Upload Build Artifact to Action + uses: actions/upload-artifact@v2.1.4 + with: + name: "ChameleonBuild" + path: "FirmwareBuild/**" diff --git a/.github/workflows/firmware-push.yml b/.github/workflows/firmware-push.yml new file mode 100644 index 0000000..f818b87 --- /dev/null +++ b/.github/workflows/firmware-push.yml @@ -0,0 +1,52 @@ +name: Firmware Build (proxgrind Branch Push, Pre-release with Artifacts) + +on: + push: + branches: [ proxgrind ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Make a firmware build folder + run: mkdir FirmwareBuild + - name: Print Kernel Ver + run: uname -a + - name: Update APT + run: sudo apt-get update -yqq + - name: Install AVR GCC Suite + run: sudo apt-get install -yqq make autoconf build-essential ca-certificates pkg-config libreadline-dev gcc-avr binutils-avr gdb-avr avr-libc avrdude + - name: Make Firmware + run: make + working-directory: Firmware/Chameleon-Mini/ + - name: Move hex file to FirmwareBuild + run: mv Chameleon*.hex $GITHUB_WORKSPACE/FirmwareBuild/ + working-directory: Firmware/Chameleon-Mini/ + - name: Move eep file to FirmwareBuild + run: mv Chameleon*.eep $GITHUB_WORKSPACE/FirmwareBuild/ + working-directory: Firmware/Chameleon-Mini/ + - name: Upload Build Artifact to Action + uses: actions/upload-artifact@v2.1.4 + with: + name: "ChameleonBuild" + path: "FirmwareBuild/**" + - name: Create a Pre-release + uses: actions/create-release@v1.1.3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: "${{ format('Build-{0}', github.sha) }}" + release_name: Firmware Build ${{ github.sha }} + body: Built at commit ${{ github.sha }} from ${{ github.actor }} + draft: false + prerelease: true + - name: Upload Pre-release Artifacts + uses: linuxgemini/github-upload-release-artifacts-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + created_tag: "${{ format('Build-{0}', github.sha) }}" + args: "FirmwareBuild/" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d6b6a0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ + +.DS_Store +*.pyc diff --git a/Chameleon_Work_20200603-262956-o_1e9sqf190sg18bc1ftd121nr7pr-uid-2340331.apk b/Chameleon_Work_20200603-262956-o_1e9sqf190sg18bc1ftd121nr7pr-uid-2340331.apk new file mode 100644 index 0000000..1402b58 Binary files /dev/null and b/Chameleon_Work_20200603-262956-o_1e9sqf190sg18bc1ftd121nr7pr-uid-2340331.apk differ diff --git a/Doc/DoxygenPages/Buttons.txt b/Doc/DoxygenPages/Buttons.txt index 70b75d2..35db309 100644 --- a/Doc/DoxygenPages/Buttons.txt +++ b/Doc/DoxygenPages/Buttons.txt @@ -29,6 +29,7 @@ * `RECALL_MEM` | Recalls a setting from the permanent Flash memory. Equivalent to the `RECALL` command. * `TOGGLE_FIELD` | Activates the reader field if it was deactivated, deactivates the reader field if it was activated. * `STORE_LOG` | Writes the current log from SRAM to FRAM and clears the SRAM log. Equivalent to the `STORE_LOG` command. + * `CLONE_MFU` | Triggers the clone of a Mifare Ultralight card in range of the antenna to the selected slot and configures it to emulate it. Equivalent to the `CLONE_MFU` command. * * Note the UID commands have no effect when the slot is configured as reader. */ \ No newline at end of file diff --git a/Doc/DoxygenPages/CommandLine.txt b/Doc/DoxygenPages/CommandLine.txt index fe0628b..5e848a5 100644 --- a/Doc/DoxygenPages/CommandLine.txt +++ b/Doc/DoxygenPages/CommandLine.txt @@ -111,6 +111,7 @@ * `SEND_RAW `| Does NOT add parity bits, sends the given byte string and returns the cards answer * `GETUID` | Obtains the UID of a card that is in the range of the antenna and returns it. This command is a \ref Anchor_TimeoutCommands "Timeout command". * `DUMP_MFU` | Reads the whole content of a Mifare Ultralight card that is in the range of the antenna and returns it. This command is a \ref Anchor_TimeoutCommands "Timeout command". + * `CLONE_MFU` | Clones a Mifare Ultralight card that is in the range of the antenna to the current slot, which is then accordingly configured to emulate it. This command is a \ref Anchor_TimeoutCommands "Timeout command". * `IDENTIFY` | Identifies the type of a card in the range of the antenna and returns it. This command is a \ref Anchor_TimeoutCommands "Timeout command". * `THRESHOLD=?` | Returns the possible number range for the reader threshold. * `THRESHOLD=` | Globally sets the reader threshold. The influences the reader function and range. Setting a wrong value may result in malfunctioning of the reader. DEFAULT: 400 diff --git a/Firmware/Chameleon-Mini/AntennaLevel.h b/Firmware/Chameleon-Mini/AntennaLevel.h index 3b7d141..6d7dc20 100644 --- a/Firmware/Chameleon-Mini/AntennaLevel.h +++ b/Firmware/Chameleon-Mini/AntennaLevel.h @@ -24,6 +24,7 @@ static inline void AntennaLevelInit(void) { + ADCA.CAL = (PRODSIGNATURES_ADCACAL1 << 8) | PRODSIGNATURES_ADCACAL0; /* Load calibration data, source: https://www.avrfreaks.net/comment/2080211#comment-2080211 */ ADCA.CTRLA = ADC_ENABLE_bm; ADCA.CTRLB = ADC_RESOLUTION_12BIT_gc; ADCA.REFCTRL = ADC_REFSEL_INT1V_gc | ADC_BANDGAP_bm; diff --git a/Firmware/Chameleon-Mini/Application/Application.h b/Firmware/Chameleon-Mini/Application/Application.h index c773a22..53caa39 100644 --- a/Firmware/Chameleon-Mini/Application/Application.h +++ b/Firmware/Chameleon-Mini/Application/Application.h @@ -21,6 +21,8 @@ #include "TITagitstandard.h" #include "Sniff14443A.h" #include "EM4233.h" +#include "NTAG215.h" +#include "Sniff15693.h" /* Function wrappers */ INLINE void ApplicationInit(void) { @@ -53,4 +55,20 @@ INLINE void ApplicationSetUid(ConfigurationUidType Uid) { LogEntry(LOG_INFO_UID_SET, Uid, ActiveConfiguration.UidSize); } +INLINE void ApplicationGetSak(uint8_t * Sak) { + ActiveConfiguration.ApplicationGetSakFunc(Sak); +} + +INLINE void ApplicationSetSak(uint8_t Sak) { + ActiveConfiguration.ApplicationSetSakFunc(Sak); +} + +INLINE void ApplicationGetAtqa(uint16_t * Atqa) { + ActiveConfiguration.ApplicationGetAtqaFunc(Atqa); +} + +INLINE void ApplicationSetAtqa(uint16_t Atqa) { + ActiveConfiguration.ApplicationSetAtqaFunc(Atqa); +} + #endif /* APPLICATION_H_ */ diff --git a/Firmware/Chameleon-Mini/Application/EM4233.c b/Firmware/Chameleon-Mini/Application/EM4233.c index 0bf28b2..5a8856a 100644 --- a/Firmware/Chameleon-Mini/Application/EM4233.c +++ b/Firmware/Chameleon-Mini/Application/EM4233.c @@ -23,9 +23,6 @@ static enum { } State; bool loggedIn; -uint8_t MyAFI; /* This variable holds current tag's AFI (is used in inventory) */ - -CurrentFrame FrameInfo; void EM4233AppInit(void) { State = STATE_READY; @@ -38,7 +35,7 @@ void EM4233AppInit(void) { FrameInfo.Selected = false; loggedIn = false; MemoryReadBlock(&MyAFI, EM4233_MEM_AFI_ADDRESS, 1); - + MemoryReadBlock(&Uid, EM4233_MEM_UID_ADDRESS, ActiveConfiguration.UidSize); } void EM4233AppReset(void) { @@ -51,6 +48,8 @@ void EM4233AppReset(void) { FrameInfo.Addressed = false; FrameInfo.Selected = false; loggedIn = false; + MemoryReadBlock(&MyAFI, EM4233_MEM_AFI_ADDRESS, 1); + MemoryReadBlock(&Uid, EM4233_MEM_UID_ADDRESS, ActiveConfiguration.UidSize); } void EM4233AppTask(void) { @@ -62,7 +61,7 @@ void EM4233AppTick(void) { } uint16_t EM4233_Lock_Block(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; uint8_t BlockAddress = *FrameInfo.Parameters; uint8_t LockStatus = 0; @@ -92,7 +91,7 @@ uint16_t EM4233_Lock_Block(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233_Write_Single(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; uint8_t BlockAddress = *FrameInfo.Parameters; uint8_t *Dataptr = FrameInfo.Parameters + 0x01; /* Data to write begins on 2nd byte of the frame received by the reader */ uint8_t LockStatus = 0; @@ -127,7 +126,7 @@ uint16_t EM4233_Write_Single(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233_Read_Single(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; uint8_t FramePtr; /* holds the address where block's data will be put */ uint8_t BlockAddress = FrameInfo.Parameters[0]; uint8_t LockStatus = 0; @@ -138,7 +137,7 @@ uint16_t EM4233_Read_Single(uint8_t *FrameBuf, uint16_t FrameBytes) { if (BlockAddress >= EM4233_NUMBER_OF_BLCKS) { /* check if the reader is requesting a sector out of bound */ if (FrameInfo.Addressed) { /* If the request is addressed */ FrameBuf[ISO15693_ADDR_FLAGS] = ISO15693_RES_FLAG_ERROR; - FrameBuf[ISO15693_RES_ADDR_PARAM] = 0x0F; /* Magic number from real tag */ + FrameBuf[ISO15693_RES_ADDR_PARAM] = ISO15693_RES_ERR_GENERIC; ResponseByteCount += 2; /* Copied this behaviour from real tag, not specified in ISO documents */ } return ResponseByteCount; /* If not addressed real tag does not respond */ @@ -168,7 +167,7 @@ uint16_t EM4233_Read_Single(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233_Read_Multiple(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; uint8_t FramePtr; /* holds the address where block's data will be put */ uint8_t BlockAddress = FrameInfo.Parameters[0]; uint8_t BlocksNumber = FrameInfo.Parameters[1] + 0x01; /* according to ISO standard, we have to read 0x08 blocks if we get 0x07 in request */ @@ -179,7 +178,7 @@ uint16_t EM4233_Read_Multiple(uint8_t *FrameBuf, uint16_t FrameBytes) { if (BlockAddress >= EM4233_NUMBER_OF_BLCKS) { /* the reader is requesting a block out of bound */ if (FrameInfo.Addressed) { /* If the request is addressed */ FrameBuf[ISO15693_ADDR_FLAGS] = ISO15693_RES_FLAG_ERROR; - FrameBuf[ISO15693_RES_ADDR_PARAM] = 0x0F; /* Magic number from real tag */ + FrameBuf[ISO15693_RES_ADDR_PARAM] = ISO15693_RES_ERR_GENERIC; ResponseByteCount += 2; /* Copied this behaviour from real tag, not specified in ISO documents */ } return ResponseByteCount; /* If not addressed real tag does not respond */ @@ -224,7 +223,7 @@ uint16_t EM4233_Read_Multiple(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233_Write_AFI(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; uint8_t AFI = FrameInfo.Parameters[0]; uint8_t LockStatus = 0; @@ -251,7 +250,7 @@ uint16_t EM4233_Write_AFI(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233_Lock_AFI(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; uint8_t LockStatus = 0; if (FrameInfo.ParamLen != 0) @@ -278,7 +277,7 @@ uint16_t EM4233_Lock_AFI(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233_Write_DSFID(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; uint8_t DSFID = FrameInfo.Parameters[0]; uint8_t LockStatus = 0; @@ -304,7 +303,7 @@ uint16_t EM4233_Write_DSFID(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233_Lock_DSFID(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; uint8_t LockStatus = 0; if (FrameInfo.ParamLen != 0) @@ -331,7 +330,7 @@ uint16_t EM4233_Lock_DSFID(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint8_t EM4233_Get_SysInfo(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; uint8_t FramePtr; /* holds the address where block's data will be put */ if (FrameInfo.ParamLen != 0) @@ -354,8 +353,6 @@ uint8_t EM4233_Get_SysInfo(uint8_t *FrameBuf, uint16_t FrameBytes) { ResponseByteCount += 1; /* Increment the response count */ /* Then append UID */ - uint8_t Uid[ActiveConfiguration.UidSize]; - EM4233GetUid(Uid); ISO15693CopyUid(&FrameBuf[FramePtr], Uid); FramePtr += ISO15693_GENERIC_UID_SIZE; /* Move forward the buffer data pointer */ ResponseByteCount += ISO15693_GENERIC_UID_SIZE; /* Increment the response count */ @@ -398,7 +395,7 @@ uint8_t EM4233_Get_SysInfo(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233_Get_Multi_Block_Sec_Stat(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; uint8_t FramePtr; /* holds the address where block's data will be put */ uint8_t BlockAddress = FrameInfo.Parameters[0]; uint8_t BlocksNumber = FrameInfo.Parameters[1] + 0x01; @@ -427,7 +424,7 @@ uint16_t EM4233_Get_Multi_Block_Sec_Stat(uint8_t *FrameBuf, uint16_t FrameBytes) } uint16_t EM4233_Select(uint8_t *FrameBuf, uint16_t FrameBytes, uint8_t *Uid) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; /* I've no idea how this request could generate errors ._. if ( ) { FrameBuf[ISO15693_ADDR_FLAGS] = ISO15693_RES_FLAG_ERROR; @@ -463,7 +460,7 @@ uint16_t EM4233_Select(uint8_t *FrameBuf, uint16_t FrameBytes, uint8_t *Uid) { } uint16_t EM4233_Reset_To_Ready(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; /* I've no idea how this request could generate errors ._. if ( ) { FrameBuf[ISO15693_ADDR_FLAGS] = ISO15693_RES_FLAG_ERROR; @@ -487,7 +484,7 @@ uint16_t EM4233_Reset_To_Ready(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233_Login(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; uint8_t Password[4] = { 0 }; if (FrameInfo.ParamLen != 4 || !FrameInfo.Addressed || !(FrameInfo.Selected && State == STATE_SELECTED)) @@ -523,7 +520,7 @@ uint16_t EM4233_Login(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233_Auth1(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; // uint8_t KeyNo = *FrameInfo.Parameters; /* Right now this parameter is unused, but it will be useful */ if (FrameInfo.ParamLen != 1) /* Malformed: not enough or too much data */ @@ -548,7 +545,7 @@ uint16_t EM4233_Auth1(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233_Auth2(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; + ResponseByteCount = ISO15693_APP_NO_RESPONSE; // uint8_t A2 = FrameInfo.Parameters; // uint8_t f = FrameInfo.Parameters + 0x08; // uint8_t g[3] = { 0 }; /* Names according to EM Marin definitions */ @@ -566,9 +563,7 @@ uint16_t EM4233_Auth2(uint8_t *FrameBuf, uint16_t FrameBytes) { } uint16_t EM4233AppProcess(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; - uint8_t Uid[ActiveConfiguration.UidSize]; - EM4233GetUid(Uid); + ResponseByteCount = ISO15693_APP_NO_RESPONSE; if ((FrameBytes < ISO15693_MIN_FRAME_SIZE) || !ISO15693CheckCRC(FrameBuf, FrameBytes - ISO15693_CRC16_SIZE)) /* malformed frame */ @@ -585,8 +580,8 @@ uint16_t EM4233AppProcess(uint8_t *FrameBuf, uint16_t FrameBytes) { return ISO15693_APP_NO_RESPONSE; if (State == STATE_READY || State == STATE_SELECTED) { - - if (*FrameInfo.Command == ISO15693_CMD_INVENTORY) { + switch (*FrameInfo.Command) { + case ISO15693_CMD_INVENTORY: if (FrameInfo.ParamLen == 0) return ISO15693_APP_NO_RESPONSE; /* malformed: not enough or too much data */ @@ -596,59 +591,77 @@ uint16_t EM4233AppProcess(uint8_t *FrameBuf, uint16_t FrameBytes) { ISO15693CopyUid(&FrameBuf[ISO15693_RES_ADDR_PARAM + 0x01], Uid); ResponseByteCount += 10; } + break; - } else if ((*FrameInfo.Command == ISO15693_CMD_STAY_QUIET) && FrameInfo.Addressed) { - State = STATE_QUIET; + case ISO15693_CMD_STAY_QUIET: + if (FrameInfo.Addressed) + State = STATE_QUIET; + break; - } else if (*FrameInfo.Command == ISO15693_CMD_READ_SINGLE) { + case ISO15693_CMD_READ_SINGLE: ResponseByteCount = EM4233_Read_Single(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == ISO15693_CMD_WRITE_SINGLE) { + case ISO15693_CMD_WRITE_SINGLE: ResponseByteCount = EM4233_Write_Single(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == ISO15693_CMD_LOCK_BLOCK) { + case ISO15693_CMD_LOCK_BLOCK: ResponseByteCount = EM4233_Lock_Block(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == ISO15693_CMD_READ_MULTIPLE) { + case ISO15693_CMD_READ_MULTIPLE: ResponseByteCount = EM4233_Read_Multiple(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == ISO15693_CMD_WRITE_AFI) { + case ISO15693_CMD_WRITE_AFI: ResponseByteCount = EM4233_Write_AFI(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == ISO15693_CMD_LOCK_AFI) { + case ISO15693_CMD_LOCK_AFI: ResponseByteCount = EM4233_Lock_AFI(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == ISO15693_CMD_WRITE_DSFID) { + case ISO15693_CMD_WRITE_DSFID: ResponseByteCount = EM4233_Write_DSFID(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == ISO15693_CMD_LOCK_DSFID) { + case ISO15693_CMD_LOCK_DSFID: ResponseByteCount = EM4233_Lock_DSFID(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == ISO15693_CMD_GET_SYS_INFO) { + case ISO15693_CMD_GET_SYS_INFO: ResponseByteCount = EM4233_Get_SysInfo(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == ISO15693_CMD_GET_BLOCK_SEC) { + case ISO15693_CMD_GET_BLOCK_SEC: ResponseByteCount = EM4233_Get_Multi_Block_Sec_Stat(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == ISO15693_CMD_RESET_TO_READY) { + case ISO15693_CMD_RESET_TO_READY: ResponseByteCount = EM4233_Reset_To_Ready(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == EM4233_CMD_LOGIN) { + case EM4233_CMD_LOGIN: ResponseByteCount = EM4233_Login(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == EM4233_CMD_AUTH1) { + case EM4233_CMD_AUTH1: ResponseByteCount = EM4233_Auth1(FrameBuf, FrameBytes); + break; - } else if (*FrameInfo.Command == EM4233_CMD_AUTH2) { + case EM4233_CMD_AUTH2: ResponseByteCount = EM4233_Auth2(FrameBuf, FrameBytes); + break; - } else { + default: if (FrameInfo.Addressed) { FrameBuf[ISO15693_ADDR_FLAGS] = ISO15693_RES_FLAG_ERROR; FrameBuf[ISO15693_RES_ADDR_PARAM] = ISO15693_RES_ERR_NOT_SUPP; ResponseByteCount = 2; } /* EM4233 respond with error flag only to addressed commands */ - } + break; +} } else if (State == STATE_QUIET) { if (*FrameInfo.Command == ISO15693_CMD_RESET_TO_READY) { @@ -656,12 +669,6 @@ uint16_t EM4233AppProcess(uint8_t *FrameBuf, uint16_t FrameBytes) { } } - if (ResponseByteCount > 0) { - /* There is data to send. Append CRC */ - ISO15693AppendCRC(FrameBuf, ResponseByteCount); - ResponseByteCount += ISO15693_CRC16_SIZE; - } - return ResponseByteCount; } @@ -669,6 +676,7 @@ void EM4233GetUid(ConfigurationUidType Uid) { MemoryReadBlock(&Uid[0], EM4233_MEM_UID_ADDRESS, ActiveConfiguration.UidSize); } -void EM4233SetUid(ConfigurationUidType Uid) { - MemoryWriteBlock(Uid, EM4233_MEM_UID_ADDRESS, ActiveConfiguration.UidSize); +void EM4233SetUid(ConfigurationUidType NewUid) { + memcpy(Uid, NewUid, ActiveConfiguration.UidSize); + MemoryWriteBlock(NewUid, EM4233_MEM_UID_ADDRESS, ActiveConfiguration.UidSize); } diff --git a/Firmware/Chameleon-Mini/Application/ISO15693-A.c b/Firmware/Chameleon-Mini/Application/ISO15693-A.c index f89697e..9308d9e 100644 --- a/Firmware/Chameleon-Mini/Application/ISO15693-A.c +++ b/Firmware/Chameleon-Mini/Application/ISO15693-A.c @@ -2,6 +2,11 @@ #include "../Common.h" #include +CurrentFrame FrameInfo; +uint8_t Uid[ISO15693_GENERIC_UID_SIZE]; +uint8_t MyAFI; +uint16_t ResponseByteCount; + //Refer to ISO/IEC 15693-3:2001 page 41 uint16_t calculateCRC(void *FrameBuf, uint16_t FrameBufSize) { uint16_t reg = ISO15693_CRC16_PRESET; diff --git a/Firmware/Chameleon-Mini/Application/ISO15693-A.h b/Firmware/Chameleon-Mini/Application/ISO15693-A.h index d4fc2ab..61d6701 100644 --- a/Firmware/Chameleon-Mini/Application/ISO15693-A.h +++ b/Firmware/Chameleon-Mini/Application/ISO15693-A.h @@ -85,6 +85,10 @@ typedef struct { bool Addressed; bool Selected; } CurrentFrame; +extern CurrentFrame FrameInfo; /* Holds current frame information */ +extern uint8_t Uid[]; +extern uint8_t MyAFI; /* Holds current tag's AFI, used during inventory */ +extern uint16_t ResponseByteCount; /* Length of response, used when building response frames */ void ISO15693AppendCRC(uint8_t *FrameBuf, uint16_t FrameBufSize); bool ISO15693CheckCRC(void *FrameBuf, uint16_t FrameBufSize); diff --git a/Firmware/Chameleon-Mini/Application/MifareClassic.c b/Firmware/Chameleon-Mini/Application/MifareClassic.c index b0cb24c..4c95ad3 100644 --- a/Firmware/Chameleon-Mini/Application/MifareClassic.c +++ b/Firmware/Chameleon-Mini/Application/MifareClassic.c @@ -5,8 +5,8 @@ * Author: skuser * * ChangeLog - * 2019-09-22 willok 加入了UID模式开关,加入了SAK模式开关 - * 2019-10-05 Willok 将侦测卡功能整合进来,以减少程序大小 + * 2019-09-22 willok Added UID mode switch and SAK mode switch + * 2019-10-05 Willok Integrate detection card function to reduce program size * */ @@ -82,7 +82,7 @@ #define CMD_TRANSFER 0xB0 #define CMD_TRANSFER_FRAME_SIZE 2 /* Bytes without CRCA */ -// 白卡相关的指令 +// White card related instructions #define CMD_CHINESE_UNLOCK 0x40 #define CMD_CHINESE_WIPE 0x41 #define CMD_CHINESE_UNLOCK_RW 0x43 @@ -455,7 +455,7 @@ void DetectionInit(void) { // Download log bool RfLogMemLoadBlock(void *Buffer, uint32_t BlockAddress, uint16_t ByteCount) { - // 位置超出尾部了,结束 + // The position is beyond the end if (BlockAddress < (DetectionLogPtr - FRAM_DETECTION_START_ADDR)) { MemoryReadBlock(Buffer, BlockAddress + FRAM_DETECTION_START_ADDR, ByteCount); if (0 == BlockAddress) @@ -1300,3 +1300,19 @@ void MifareClassicSetUid(ConfigurationUidType Uid) { MemoryWriteBlock(&BCC, MEM_UID_BCC1_ADDRESS, ISO14443A_CL_BCC_SIZE); } } + +void MifareClassicGetAtqa(uint16_t * Atqa) { + *Atqa = CardATQAValue; +} + +void MifareClassicSetAtqa(uint16_t Atqa) { + CardATQAValue = Atqa; +} + +void MifareClassicGetSak(uint8_t * Sak) { + *Sak = CardSAKValue; +} + +void MifareClassicSetSak(uint8_t Sak) { + CardSAKValue = Sak; +} \ No newline at end of file diff --git a/Firmware/Chameleon-Mini/Application/MifareClassic.h b/Firmware/Chameleon-Mini/Application/MifareClassic.h index d509f2c..feab77a 100644 --- a/Firmware/Chameleon-Mini/Application/MifareClassic.h +++ b/Firmware/Chameleon-Mini/Application/MifareClassic.h @@ -33,5 +33,9 @@ uint16_t MifareClassicAppProcess(uint8_t *Buffer, uint16_t BitCount); void MifareClassicGetUid(ConfigurationUidType Uid); void MifareClassicSetUid(ConfigurationUidType Uid); +void MifareClassicGetAtqa(uint16_t * Atqa); +void MifareClassicSetAtqa(uint16_t Atqa); +void MifareClassicGetSak(uint8_t * Sak); +void MifareClassicSetSak(uint8_t Sak); #endif /* MIFARECLASSIC_H_ */ diff --git a/Firmware/Chameleon-Mini/Application/MifareUltralight.c b/Firmware/Chameleon-Mini/Application/MifareUltralight.c index 1ce11c2..654c310 100644 --- a/Firmware/Chameleon-Mini/Application/MifareUltralight.c +++ b/Firmware/Chameleon-Mini/Application/MifareUltralight.c @@ -112,6 +112,8 @@ static uint8_t CompatWritePageAddress; static bool Authenticated; static uint8_t FirstAuthenticatedPage; static bool ReadAccessProtected; +static uint16_t CardATQAValue; +static uint8_t CardSAKValue; static uint8_t RNDBBuff [8]; static uint8_t InitialVector[8] = {0}; static uint8_t TripleDesKey [16]; @@ -153,6 +155,8 @@ static void AppInitCommon(void) { FromHalt = false; Authenticated = false; ArmedForCompatWrite = false; + CardATQAValue = ATQA_VALUE; + CardSAKValue = SAK_CL1_VALUE; } void MifareUltralightCAppInit(void) { Flavor = UL_C; @@ -285,7 +289,7 @@ static uint16_t AppProcess(uint8_t *const Buffer, uint16_t ByteCount) { //Handle MF ULC counter if (CompatWritePageAddress == MF_ULC_COUNTER_ADDRESS && Flavor == UL_C) { - if (IncrementCounter(&Buffer[2])) { + if (IncrementCounter(&Buffer[0])) { Buffer[0] = ACK_VALUE; return ACK_FRAME_SIZE; } else { @@ -294,7 +298,7 @@ static uint16_t AppProcess(uint8_t *const Buffer, uint16_t ByteCount) { } } - AppWritePage(CompatWritePageAddress, &Buffer[2]); + AppWritePage(CompatWritePageAddress, &Buffer[0]); Buffer[0] = ACK_VALUE; return ACK_FRAME_SIZE; } @@ -673,3 +677,22 @@ void MifareUltralightSetUid(ConfigurationUidType Uid) { MemoryWriteBlock(&BCC2, UID_BCC2_ADDRESS, ISO14443A_CL_BCC_SIZE); } +void MifareUltralightGetAtqa(uint16_t * Atqa) +{ + *Atqa = CardATQAValue; +} + +void MifareUltralightSetAtqa(uint16_t Atqa) +{ + CardATQAValue = Atqa; +} + +void MifareUltralightGetSak(uint8_t * Sak) +{ + *Sak = CardSAKValue; +} + +void MifareUltralightSetSak(uint8_t Sak) +{ + CardSAKValue = Sak; +} diff --git a/Firmware/Chameleon-Mini/Application/MifareUltralight.h b/Firmware/Chameleon-Mini/Application/MifareUltralight.h index 91ac96b..09f436b 100644 --- a/Firmware/Chameleon-Mini/Application/MifareUltralight.h +++ b/Firmware/Chameleon-Mini/Application/MifareUltralight.h @@ -39,6 +39,9 @@ uint16_t MifareUltralightAppProcess(uint8_t *Buffer, uint16_t BitCount); void MifareUltralightGetUid(ConfigurationUidType Uid); void MifareUltralightSetUid(ConfigurationUidType Uid); - +void MifareUltralightGetAtqa(uint16_t * Atqa); +void MifareUltralightSetAtqa(uint16_t Atqa); +void MifareUltralightGetSak(uint8_t * Sak); +void MifareUltralightSetSak(uint8_t Sak); #endif /* MIFAREULTRALIGHT_H_ */ diff --git a/Firmware/Chameleon-Mini/Application/NTAG215.c b/Firmware/Chameleon-Mini/Application/NTAG215.c new file mode 100644 index 0000000..c7bb0e1 --- /dev/null +++ b/Firmware/Chameleon-Mini/Application/NTAG215.c @@ -0,0 +1,438 @@ +/* + * NTAG215.c + * + * Created on: 20.02.2019 + * Author: Giovanni Cammisa (gcammisa) + * Still missing support for: + * - The management of both static and dynamic lock bytes + * - Bruteforce protection (AUTHLIM COUNTER) + * Thanks to skuser for the MifareUltralight code used as a starting point + */ + +#include "ISO14443-3A.h" +#include "../Codec/ISO14443-2A.h" +#include "../Memory.h" +#include "NTAG215.h" + +// DEFINE ATQA and SAK +#define ATQA_VALUE 0x0044 +#define SAK_VALUE 0x00 + +#define SAK_CL1_VALUE ISO14443A_SAK_INCOMPLETE +#define SAK_CL2_VALUE ISO14443A_SAK_COMPLETE_NOT_COMPLIANT + +// ACK and NACK +#define ACK_VALUE 0x0A +#define ACK_FRAME_SIZE 4 /* Bits */ +#define NAK_INVALID_ARG 0x00 +#define NAK_CRC_ERROR 0x01 +#define NAK_NOT_AUTHED 0x04 +#define NAK_EEPROM_ERROR 0x05 +#define NAK_FRAME_SIZE 4 + +// DEFINING COMMANDS +/* ISO commands */ +#define CMD_HALT 0x50 +// NTAG COMMANDS +#define CMD_GET_VERSION 0x60 +#define CMD_READ 0x30 +#define CMD_FAST_READ 0x3A +#define CMD_WRITE 0xA2 +#define CMD_COMPAT_WRITE 0xA0 +#define CMD_READ_CNT 0x39 +#define CMD_PWD_AUTH 0x1B +#define CMD_READ_SIG 0x3C + +// MEMORY LAYOUT STUFF, addresses and sizes in bytes +// UID stuff +#define UID_CL1_ADDRESS 0x00 +#define UID_CL1_SIZE 3 +#define UID_BCC1_ADDRESS 0x03 +#define UID_CL2_ADDRESS 0x04 +#define UID_CL2_SIZE 4 +#define UID_BCC2_ADDRESS 0x08 +// LockBytes stuff +#define STATIC_LOCKBYTE_0_ADDRESS 0x0A +#define STATIC_LOCKBYTE_1_ADDRESS 0x0B +// CONFIG stuff +#define CONFIG_AREA_START_ADDRESS NTAG215_PAGE_SIZE * 0x83 +#define CONFIG_AREA_SIZE 8 +// CONFIG offsets, relative to config start address +#define CONF_AUTH0_OFFSET 0x03 +#define CONF_ACCESS_OFFSET 0x04 +#define CONF_PASSWORD_OFFSET 0x08 +#define CONF_PACK_OFFSET 0x0C + +// WRITE STUFF +#define BYTES_PER_WRITE 4 +#define PAGE_WRITE_MIN 0x02 + +// CONFIG masks to check individual needed bits +#define CONF_ACCESS_PROT 0x80 + +#define VERSION_INFO_LENGTH 8 //8 bytes info lenght + crc + +#define BYTES_PER_READ NTAG215_PAGE_SIZE * 4 + +// SIGNATURE Lenght +#define SIGNATURE_LENGTH 32 + + +static enum { + STATE_HALT, + STATE_IDLE, + STATE_READY1, + STATE_READY2, + STATE_ACTIVE +} State; + +static bool FromHalt = false; +static uint8_t PageCount; +static bool ArmedForCompatWrite; +static uint8_t CompatWritePageAddress; +static bool Authenticated; +static uint8_t FirstAuthenticatedPage; +static bool ReadAccessProtected; +static uint8_t Access; + + +void NTAG215AppInit(void) +{ + + State = STATE_IDLE; + FromHalt = false; + ArmedForCompatWrite = false; + Authenticated = false; + PageCount = NTAG215_PAGES; + + /* Fetch some of the configuration into RAM */ + MemoryReadBlock(&FirstAuthenticatedPage, CONFIG_AREA_START_ADDRESS + CONF_AUTH0_OFFSET, 1); + MemoryReadBlock(&Access, CONFIG_AREA_START_ADDRESS + CONF_ACCESS_OFFSET, 1); + ReadAccessProtected = !!(Access & CONF_ACCESS_PROT); +} + +void NTAG215AppReset(void) +{ + State = STATE_IDLE; +} + +void NTAG215AppTask(void) +{ + +} + +// Verify authentication +static bool VerifyAuthentication(uint8_t PageAddress) +{ + /* If authenticated, no verification needed */ + if (Authenticated) { + return true; + } + /* Otherwise, verify the accessed page is below the limit */ + return PageAddress < FirstAuthenticatedPage; +} + +// Writes a page +static uint8_t AppWritePage(uint8_t PageAddress, uint8_t* const Buffer) +{ + if (!ActiveConfiguration.ReadOnly) { + MemoryWriteBlock(Buffer, PageAddress * NTAG215_PAGE_SIZE, NTAG215_PAGE_SIZE); + } else { + /* If the chameleon is in read only mode, it silently + * ignores any attempt to write data. */ + } + return 0; +} + +// Basic sketch of the command handling stuff +static uint16_t AppProcess(uint8_t* const Buffer, uint16_t ByteCount) +{ + uint8_t Cmd = Buffer[0]; + + /* Handle the compatibility write command */ + if (ArmedForCompatWrite) { + ArmedForCompatWrite = false; + + AppWritePage(CompatWritePageAddress, &Buffer[2]); + Buffer[0] = ACK_VALUE; + return ACK_FRAME_SIZE; + } + + switch (Cmd) { + case CMD_GET_VERSION: { + /* Provide hardcoded version response */ //VERSION RESPONSE FOR NTAG 215 + Buffer[0] = 0x00; + Buffer[1] = 0x04; + Buffer[2] = 0x04; + Buffer[3] = 0x02; + Buffer[4] = 0x01; + Buffer[5] = 0x00; + Buffer[6] = 0x11; + Buffer[7] = 0x03; + ISO14443AAppendCRCA(Buffer, VERSION_INFO_LENGTH); + return (VERSION_INFO_LENGTH + ISO14443A_CRCA_SIZE) * 8; + } + + case CMD_READ: { + uint8_t PageAddress = Buffer[1]; + uint8_t PageLimit; + uint8_t Offset; + + PageLimit = PageCount; + + /* if protected and not autenticated, ensure the wraparound is at the first protected page */ + if (ReadAccessProtected && !Authenticated) { + PageLimit = FirstAuthenticatedPage; + } + else { + PageLimit = PageCount; + } + + /* Validation */ + if (PageAddress >= PageLimit) { + Buffer[0] = NAK_INVALID_ARG; + return NAK_FRAME_SIZE; + } + /* Read out, emulating the wraparound */ + for (Offset = 0; Offset < BYTES_PER_READ; Offset += 4) { + MemoryReadBlock(&Buffer[Offset], PageAddress * NTAG215_PAGE_SIZE, NTAG215_PAGE_SIZE); + PageAddress++; + if (PageAddress == PageLimit) { // if arrived ad the last page, start reading from page 0 + PageAddress = 0; + } + } + ISO14443AAppendCRCA(Buffer, BYTES_PER_READ); + return (BYTES_PER_READ + ISO14443A_CRCA_SIZE) * 8; + } + + case CMD_FAST_READ: { + uint8_t StartPageAddress = Buffer[1]; + uint8_t EndPageAddress = Buffer[2]; + /* Validation */ + if ((StartPageAddress > EndPageAddress) || (StartPageAddress >= PageCount) || (EndPageAddress >= PageCount)) { + Buffer[0] = NAK_INVALID_ARG; + return NAK_FRAME_SIZE; + } + + /* Check authentication only if protection is read&write (instead of only write protection) */ + if (ReadAccessProtected) { + if (!VerifyAuthentication(StartPageAddress) || !VerifyAuthentication(EndPageAddress)) { + Buffer[0] = NAK_NOT_AUTHED; + return NAK_FRAME_SIZE; + } + } + + ByteCount = (EndPageAddress - StartPageAddress + 1) * NTAG215_PAGE_SIZE; + MemoryReadBlock(Buffer, StartPageAddress * NTAG215_PAGE_SIZE, ByteCount); + ISO14443AAppendCRCA(Buffer, ByteCount); + return (ByteCount + ISO14443A_CRCA_SIZE) * 8; + } + + case CMD_PWD_AUTH: { + uint8_t Password[4]; + + /* For now I don't care about bruteforce protection, so: */ + /* TODO: IMPLEMENT COUNTER AUTHLIM */ + + /* Read and compare the password */ + MemoryReadBlock(Password, CONFIG_AREA_START_ADDRESS + CONF_PASSWORD_OFFSET, 4); + if (Password[0] != Buffer[1] || Password[1] != Buffer[2] || Password[2] != Buffer[3] || Password[3] != Buffer[4]) { + Buffer[0] = NAK_NOT_AUTHED; + return NAK_FRAME_SIZE; + } + /* Authenticate the user */ + //RESET AUTHLIM COUNTER, CURRENTLY NOT IMPLEMENTED + Authenticated = 1; + /* Send the PACK value back */ + MemoryReadBlock(Buffer, CONFIG_AREA_START_ADDRESS + CONF_PACK_OFFSET, 2); + ISO14443AAppendCRCA(Buffer, 2); + return (2 + ISO14443A_CRCA_SIZE) * 8; + } + + case CMD_WRITE: { + /* This is a write command containing 4 bytes of data that + * should be written to the given page address. */ + uint8_t PageAddress = Buffer[1]; + /* Validation */ + if ((PageAddress < PAGE_WRITE_MIN) || (PageAddress >= PageCount)) { + Buffer[0] = NAK_INVALID_ARG; + return NAK_FRAME_SIZE; + } + if (!VerifyAuthentication(PageAddress)) { + Buffer[0] = NAK_NOT_AUTHED; + return NAK_FRAME_SIZE; + } + AppWritePage(PageAddress, &Buffer[2]); + Buffer[0] = ACK_VALUE; + return ACK_FRAME_SIZE; + } + + case CMD_COMPAT_WRITE: { + uint8_t PageAddress = Buffer[1]; + /* Validation */ + if ((PageAddress < PAGE_WRITE_MIN) || (PageAddress >= PageCount)) { + Buffer[0] = NAK_INVALID_ARG; + return NAK_FRAME_SIZE; + } + if (!VerifyAuthentication(PageAddress)) { + Buffer[0] = NAK_NOT_AUTHED; + return NAK_FRAME_SIZE; + } + /* CRC check passed and page-address is within bounds. + * Store address and proceed to receiving the data. */ + CompatWritePageAddress = PageAddress; + ArmedForCompatWrite = true; //TODO:IMPLEMENT ARMED COMPAT WRITE + Buffer[0] = ACK_VALUE; + return ACK_FRAME_SIZE; + } + + + case CMD_READ_SIG: { + /* Hardcoded response */ + memset(Buffer, 0xCA, SIGNATURE_LENGTH); + ISO14443AAppendCRCA(Buffer, SIGNATURE_LENGTH); + return (SIGNATURE_LENGTH + ISO14443A_CRCA_SIZE) * 8; + } + + //PART OF ISO STANDARD, NOT OF NTAG DATASHEET + case CMD_HALT: { + /* Halts the tag. According to the ISO14443, the second + * byte is supposed to be 0. */ + if (Buffer[1] == 0) { + /* According to ISO14443, we must not send anything + * in order to acknowledge the HALT command. */ + State = STATE_HALT; + return ISO14443A_APP_NO_RESPONSE; + } else { + Buffer[0] = NAK_INVALID_ARG; + return NAK_FRAME_SIZE; + } + } + + + default: { + break; + } + + } + /* Command not handled. Switch to idle. */ + + State = STATE_IDLE; + return ISO14443A_APP_NO_RESPONSE; + +} + + +// FINITE STATE MACHINE STUFF, SHOULD BE THE VERY SIMILAR TO Mifare Ultralight +uint16_t NTAG215AppProcess(uint8_t* Buffer, uint16_t BitCount) +{ + uint8_t Cmd = Buffer[0]; + uint16_t ByteCount; + + switch(State) { + case STATE_IDLE: + case STATE_HALT: + FromHalt = State == STATE_HALT; + if (ISO14443AWakeUp(Buffer, &BitCount, ATQA_VALUE, FromHalt)) { + /* We received a REQA or WUPA command, so wake up. */ + State = STATE_READY1; + return BitCount; + } + break; + + case STATE_READY1: + if (ISO14443AWakeUp(Buffer, &BitCount, ATQA_VALUE, FromHalt)) { + State = FromHalt ? STATE_HALT : STATE_IDLE; + return ISO14443A_APP_NO_RESPONSE; + } else if (Cmd == ISO14443A_CMD_SELECT_CL1) { + /* Load UID CL1 and perform anticollision. Since + * MF Ultralight use a double-sized UID, the first byte + * of CL1 has to be the cascade-tag byte. */ + uint8_t UidCL1[ISO14443A_CL_UID_SIZE] = { [0] = ISO14443A_UID0_CT }; + + MemoryReadBlock(&UidCL1[1], UID_CL1_ADDRESS, UID_CL1_SIZE); + + if (ISO14443ASelect(Buffer, &BitCount, UidCL1, SAK_CL1_VALUE)) { + /* CL1 stage has ended successfully */ + State = STATE_READY2; + } + + return BitCount; + } else { + /* Unknown command. Enter halt state */ + State = STATE_IDLE; + } + break; + + case STATE_READY2: + if (ISO14443AWakeUp(Buffer, &BitCount, ATQA_VALUE, FromHalt)) { + State = FromHalt ? STATE_HALT : STATE_IDLE; + return ISO14443A_APP_NO_RESPONSE; + } else if (Cmd == ISO14443A_CMD_SELECT_CL2) { + /* Load UID CL2 and perform anticollision */ + uint8_t UidCL2[ISO14443A_CL_UID_SIZE]; + + MemoryReadBlock(UidCL2, UID_CL2_ADDRESS, UID_CL2_SIZE); + + if (ISO14443ASelect(Buffer, &BitCount, UidCL2, SAK_CL2_VALUE)) { + /* CL2 stage has ended successfully. This means + * our complete UID has been sent to the reader. */ + State = STATE_ACTIVE; + } + + return BitCount; + } else { + /* Unknown command. Enter halt state */ + State = STATE_IDLE; + } + break; + + // Only ACTIVE state, no AUTHENTICATED state, PWD_AUTH is handled in commands. + case STATE_ACTIVE: + /* Preserve incoming data length */ + ByteCount = (BitCount + 7) >> 3; + if (ISO14443AWakeUp(Buffer, &BitCount, ATQA_VALUE, FromHalt)) { + State = FromHalt ? STATE_HALT : STATE_IDLE; + return ISO14443A_APP_NO_RESPONSE; + } + /* At the very least, there should be 3 bytes in the buffer. */ + if (ByteCount < (1 + ISO14443A_CRCA_SIZE)) { + State = STATE_IDLE; + return ISO14443A_APP_NO_RESPONSE; + } + /* All commands here have CRCA appended; verify it right away */ + ByteCount -= 2; + if (!ISO14443ACheckCRCA(Buffer, ByteCount)) { + Buffer[0] = NAK_CRC_ERROR; + return NAK_FRAME_SIZE; + } + return AppProcess(Buffer, ByteCount); + + default: + /* Unknown state? Should never happen. */ + break; + } + + /* No response has been sent, when we reach here */ + return ISO14443A_APP_NO_RESPONSE; +} + +// HELPER FUNCTIONS +void NTAG215GetUid(ConfigurationUidType Uid) +{ + /* Read UID from memory */ + MemoryReadBlock(&Uid[0], UID_CL1_ADDRESS, UID_CL1_SIZE); + MemoryReadBlock(&Uid[UID_CL1_SIZE], UID_CL2_ADDRESS, UID_CL2_SIZE); +} + +void NTAG215SetUid(ConfigurationUidType Uid) +{ + /* Calculate check bytes and write everything into memory */ + uint8_t BCC1 = ISO14443A_UID0_CT ^ Uid[0] ^ Uid[1] ^ Uid[2]; + uint8_t BCC2 = Uid[3] ^ Uid[4] ^ Uid[5] ^ Uid[6]; + + MemoryWriteBlock(&Uid[0], UID_CL1_ADDRESS, UID_CL1_SIZE); + MemoryWriteBlock(&BCC1, UID_BCC1_ADDRESS, ISO14443A_CL_BCC_SIZE); + MemoryWriteBlock(&Uid[UID_CL1_SIZE], UID_CL2_ADDRESS, UID_CL2_SIZE); + MemoryWriteBlock(&BCC2, UID_BCC2_ADDRESS, ISO14443A_CL_BCC_SIZE); +} diff --git a/Firmware/Chameleon-Mini/Application/NTAG215.h b/Firmware/Chameleon-Mini/Application/NTAG215.h new file mode 100644 index 0000000..77c1cc5 --- /dev/null +++ b/Firmware/Chameleon-Mini/Application/NTAG215.h @@ -0,0 +1,27 @@ +/* + * NTAG215.h + * + * Created on: 20.02.2019 + * Author: gcammisa + */ + +#ifndef NTAG215_H_ +#define NTAG215_H_ + +#include "Application.h" +#include "ISO14443-3A.h" + +#define NTAG215_UID_SIZE ISO14443A_UID_SIZE_DOUBLE // 7 bytes UID +#define NTAG215_PAGE_SIZE 4 // bytes per page +#define NTAG215_PAGES 135 // 135 pages total, from 0 to 134 +#define NTAG215_MEM_SIZE ( NTAG215_PAGE_SIZE * NTAG215_PAGES ) + +void NTAG215AppInit(void); +void NTAG215AppReset(void); +void NTAG215AppTask(void); + +uint16_t NTAG215AppProcess(uint8_t* Buffer, uint16_t BitCount); + +void NTAG215GetUid(ConfigurationUidType Uid); +void NTAG215SetUid(ConfigurationUidType Uid); +#endif diff --git a/Firmware/Chameleon-Mini/Application/Reader14443A.c b/Firmware/Chameleon-Mini/Application/Reader14443A.c index 6d6c236..921a171 100644 --- a/Firmware/Chameleon-Mini/Application/Reader14443A.c +++ b/Firmware/Chameleon-Mini/Application/Reader14443A.c @@ -20,6 +20,9 @@ // TODO replace remaining magic numbers +uint8_t ReaderSendBuffer[CODEC_BUFFER_SIZE]; +uint16_t ReaderSendBitCount; + static bool Selected = false; Reader14443Command Reader14443CurrentCommand = Reader14443_Do_Nothing; @@ -684,6 +687,7 @@ uint16_t Reader14443AAppProcess(uint8_t *Buffer, uint16_t BitCount) { return rVal; } + case Reader14443_Clone_MF_Ultralight: case Reader14443_Read_MF_Ultralight: { static uint8_t MFURead_CurrentAdress = 0; static uint8_t MFUContents[64]; @@ -718,18 +722,28 @@ uint16_t Reader14443AAppProcess(uint8_t *Buffer, uint16_t BitCount) { if (MFURead_CurrentAdress == 16) { Selected = false; MFURead_CurrentAdress = 0; - Reader14443CurrentCommand = Reader14443_Do_Nothing; - char tmpBuf[135]; // 135 = 128 hex digits + 3 * \r\n + \0 - BufferToHexString(tmpBuf, 135, MFUContents, 16); - snprintf(tmpBuf + 32, 135 - 32, "\r\n"); - BufferToHexString(tmpBuf + 32 + 2, 135 - 32 - 2, MFUContents + 16, 16); - snprintf(tmpBuf + 32 + 2 + 32, 135 - 32 - 2 - 32, "\r\n"); - BufferToHexString(tmpBuf + 32 + 2 + 32 + 2, 135 - 32 - 2 - 32 - 2, MFUContents + 32, 16); - snprintf(tmpBuf + 32 + 2 + 32 + 2 + 32, 135 - 32 - 2 - 32 - 2 - 32, "\r\n"); - BufferToHexString(tmpBuf + 32 + 2 + 32 + 2 + 32 + 2, 135 - 32 - 2 - 32 - 2 - 32 - 2, MFUContents + 48, 16); - CodecReaderFieldStop(); - CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, tmpBuf); + if (Reader14443CurrentCommand == Reader14443_Read_MF_Ultralight) { // dump + Reader14443CurrentCommand = Reader14443_Do_Nothing; + char tmpBuf[135]; // 135 = 128 hex digits + 3 * \r\n + \0 + BufferToHexString(tmpBuf, 135, MFUContents, 16); + snprintf(tmpBuf + 32, 135 - 32, "\r\n"); + BufferToHexString(tmpBuf + 32 + 2, 135 - 32 - 2, MFUContents + 16, 16); + snprintf(tmpBuf + 32 + 2 + 32, 135 - 32 - 2 - 32, "\r\n"); + BufferToHexString(tmpBuf + 32 + 2 + 32 + 2, 135 - 32 - 2 - 32 - 2, MFUContents + 32, 16); + snprintf(tmpBuf + 32 + 2 + 32 + 2 + 32, 135 - 32 - 2 - 32 - 2 - 32, "\r\n"); + BufferToHexString(tmpBuf + 32 + 2 + 32 + 2 + 32 + 2, 135 - 32 - 2 - 32 - 2 - 32 - 2, MFUContents + 48, 16); + CodecReaderFieldStop(); + CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, tmpBuf); + } else { // clone + Reader14443CurrentCommand = Reader14443_Do_Nothing; + CodecReaderFieldStop(); + MemoryUploadBlock(&MFUContents, 0, 64); + CommandLinePendingTaskFinished(COMMAND_INFO_OK_WITH_TEXT_ID, "Card Cloned to Slot"); + ConfigurationSetById(CONFIG_MF_ULTRALIGHT); + MemoryStore(); + SettingsSave(); + } return 0; } Buffer[0] = 0x30; // MiFare Ultralight read command @@ -803,17 +817,17 @@ uint16_t Reader14443AAppProcess(uint8_t *Buffer, uint16_t BitCount) { if (CardCandidatesIdx == 1) { int cfgid = -1; switch (CardCandidates[0]) { -#ifdef CONFIG_MF_ULTRALIGHT_SUPPORT case CardType_NXP_MIFARE_Ultralight: { cfgid = CONFIG_MF_ULTRALIGHT; // TODO: enter MFU clone mdoe break; } -#endif case CardType_NXP_MIFARE_Classic_1k: case CardType_Infineon_MIFARE_Classic_1k: { if (CardCharacteristics.UIDSize == UIDSize_Single) { +#ifdef CONFIG_MF_CLASSIC_1K_SUPPORT cfgid = CONFIG_MF_CLASSIC_1K; +#endif #ifdef CONFIG_MF_CLASSIC_1K_7B_SUPPORT } else if (CardCharacteristics.UIDSize == UIDSize_Double) { cfgid = CONFIG_MF_CLASSIC_1K_7B; @@ -825,8 +839,10 @@ uint16_t Reader14443AAppProcess(uint8_t *Buffer, uint16_t BitCount) { case CardType_Nokia_MIFARE_Classic_4k_emulated_6212: case CardType_Nokia_MIFARE_Classic_4k_emulated_6131: { if (CardCharacteristics.UIDSize == UIDSize_Single) { +#ifdef CONFIG_MF_CLASSIC_4K_SUPPORT cfgid = CONFIG_MF_CLASSIC_4K; -#ifdef CONFIG_MF_CLASSIC_1K_7B_SUPPORT +#endif +#ifdef CONFIG_MF_CLASSIC_4K_7B_SUPPORT } else if (CardCharacteristics.UIDSize == UIDSize_Double) { cfgid = CONFIG_MF_CLASSIC_4K_7B; #endif diff --git a/Firmware/Chameleon-Mini/Application/Reader14443A.h b/Firmware/Chameleon-Mini/Application/Reader14443A.h index 9125470..e2e32b9 100644 --- a/Firmware/Chameleon-Mini/Application/Reader14443A.h +++ b/Firmware/Chameleon-Mini/Application/Reader14443A.h @@ -6,8 +6,8 @@ #define CRC_INIT 0x6363 -uint8_t ReaderSendBuffer[CODEC_BUFFER_SIZE]; -uint16_t ReaderSendBitCount; +extern uint8_t ReaderSendBuffer[]; +extern uint16_t ReaderSendBitCount; void Reader14443AAppInit(void); void Reader14443AAppReset(void); @@ -30,7 +30,8 @@ typedef enum { Reader14443_Autocalibrate, Reader14443_Read_MF_Ultralight, Reader14443_Identify, - Reader14443_Identify_Clone + Reader14443_Identify_Clone, + Reader14443_Clone_MF_Ultralight } Reader14443Command; diff --git a/Firmware/Chameleon-Mini/Application/Sl2s2002.c b/Firmware/Chameleon-Mini/Application/Sl2s2002.c index e8a242e..ffdbed1 100644 --- a/Firmware/Chameleon-Mini/Application/Sl2s2002.c +++ b/Firmware/Chameleon-Mini/Application/Sl2s2002.c @@ -141,12 +141,6 @@ uint16_t Sl2s2002AppProcess(uint8_t *FrameBuf, uint16_t FrameBytes) { break; } - if (ResponseByteCount > 0) { - /* There is data to be sent. Append CRC */ - ISO15693AppendCRC(FrameBuf, ResponseByteCount); - ResponseByteCount += ISO15693_CRC16_SIZE; - } - return ResponseByteCount; } else { // Invalid CRC diff --git a/Firmware/Chameleon-Mini/Application/Sniff14443A.c b/Firmware/Chameleon-Mini/Application/Sniff14443A.c index c85a177..b03aaf9 100644 --- a/Firmware/Chameleon-Mini/Application/Sniff14443A.c +++ b/Firmware/Chameleon-Mini/Application/Sniff14443A.c @@ -11,7 +11,6 @@ extern bool checkParityBits(uint8_t *Buffer, uint16_t BitCount); Sniff14443Command Sniff14443CurrentCommand = Sniff14443_Do_Nothing; -//bool selected = false; static enum { STATE_IDLE, STATE_REQA, @@ -68,7 +67,7 @@ uint16_t Sniff14443AAppProcess(uint8_t *Buffer, uint16_t BitCount) { case STATE_REQA: LED_PORT.OUTCLR = LED_RED; // If received Reader REQA or WUPA - if (TrafficSource == TRAFFIC_READER && + if (SniffTrafficSource == TRAFFIC_READER && (Buffer[0] == 0x26 || Buffer[0] == 0x52)) { SniffState = STATE_ATQA; } else { @@ -77,7 +76,7 @@ uint16_t Sniff14443AAppProcess(uint8_t *Buffer, uint16_t BitCount) { break; case STATE_ATQA: // ATQA: P RRRR XXXX P XXRX XXXX - if (TrafficSource == TRAFFIC_CARD && + if (SniffTrafficSource == TRAFFIC_CARD && BitCount == 2 * 9 && (Buffer[0] & 0x20) == 0x00 && // Bit6 RFU shall be 0 (Buffer[1] & 0xE0) == 0x00 && // bit13-16 RFU shall be 0 @@ -88,7 +87,7 @@ uint16_t Sniff14443AAppProcess(uint8_t *Buffer, uint16_t BitCount) { } else { // If not ATQA, but REQA, then stay on this state, // Reset to REQA, save the counter and reset the counter - if (TrafficSource == TRAFFIC_READER && + if (SniffTrafficSource == TRAFFIC_READER && (Buffer[0] == 0x26 || Buffer[0] == 0x52)) { } else { // If not ATQA and not REQA then reset to REQA @@ -98,7 +97,7 @@ uint16_t Sniff14443AAppProcess(uint8_t *Buffer, uint16_t BitCount) { break; case STATE_ANTICOLLI: // SEL: 93/95/97 - if (TrafficSource == TRAFFIC_READER && + if (SniffTrafficSource == TRAFFIC_READER && BitCount == 2 * 8 && (Buffer[0] & 0xf0) == 0x90 && (Buffer[0] & 0x09) == 0x01) { @@ -109,7 +108,7 @@ uint16_t Sniff14443AAppProcess(uint8_t *Buffer, uint16_t BitCount) { } break; case STATE_UID: - if (TrafficSource == TRAFFIC_CARD && + if (SniffTrafficSource == TRAFFIC_CARD && BitCount == 5 * 9 && checkParityBits(Buffer, BitCount)) { SniffState = STATE_SELECT; @@ -120,7 +119,7 @@ uint16_t Sniff14443AAppProcess(uint8_t *Buffer, uint16_t BitCount) { case STATE_SELECT: // SELECT: 9 bytes, SEL = 93/95/97, NVB=70 - if (TrafficSource == TRAFFIC_READER && + if (SniffTrafficSource == TRAFFIC_READER && BitCount == 9 * 8 && (Buffer[0] & 0xf0) == 0x90 && (Buffer[0] & 0x09) == 0x01 && @@ -133,7 +132,7 @@ uint16_t Sniff14443AAppProcess(uint8_t *Buffer, uint16_t BitCount) { break; case STATE_SAK: // SAK: 1Byte SAK + CRC - if (TrafficSource == TRAFFIC_CARD && + if (SniffTrafficSource == TRAFFIC_CARD && BitCount == 3 * 9 && checkParityBits(Buffer, BitCount)) { if ((Buffer[0] & 0x04) == 0x00) { diff --git a/Firmware/Chameleon-Mini/Application/Sniff15693.c b/Firmware/Chameleon-Mini/Application/Sniff15693.c new file mode 100644 index 0000000..67da470 --- /dev/null +++ b/Firmware/Chameleon-Mini/Application/Sniff15693.c @@ -0,0 +1,38 @@ +/* + * SniffISO15693.h + * + * Created on: 05.11.2019 + * Author: ceres-c + */ + +#ifdef CONFIG_ISO15693_SNIFF_SUPPORT + +#include "../Codec/SniffISO15693.h" +#include "Sniff15693.h" + +void SniffISO15693AppInit(void) +{ +} + +void SniffISO15693AppReset(void) +{ +} + + +void SniffISO15693AppTask(void) +{ + +} + +void SniffISO15693AppTick(void) +{ + + +} + +uint16_t SniffISO15693AppProcess(uint8_t* FrameBuf, uint16_t FrameBytes) +{ + return 0; +} + +#endif /* CONFIG_ISO15693_SNIFF_SUPPORT */ diff --git a/Firmware/Chameleon-Mini/Application/Sniff15693.h b/Firmware/Chameleon-Mini/Application/Sniff15693.h new file mode 100644 index 0000000..46920d9 --- /dev/null +++ b/Firmware/Chameleon-Mini/Application/Sniff15693.h @@ -0,0 +1,20 @@ +/* + * SniffISO15693.h + * + * Created on: 05.11.2019 + * Author: ceres-c + */ + +#ifndef SNIFF_15693_H_ +#define SNIFF_15693_H_ + +#include "Application.h" +#include "ISO15693-A.h" + +void SniffISO15693AppInit(void); +void SniffISO15693AppReset(void); +void SniffISO15693AppTask(void); +void SniffISO15693AppTick(void); +uint16_t SniffISO15693AppProcess(uint8_t* FrameBuf, uint16_t FrameBytes); + +#endif /* SNIFF_15693_H_ */ diff --git a/Firmware/Chameleon-Mini/Application/TITagitstandard.c b/Firmware/Chameleon-Mini/Application/TITagitstandard.c index 4ed2c5c..2d4618c 100644 --- a/Firmware/Chameleon-Mini/Application/TITagitstandard.c +++ b/Firmware/Chameleon-Mini/Application/TITagitstandard.c @@ -16,10 +16,8 @@ static enum { STATE_QUIET } State; -uint8_t MyAFI; /* Holds current tag's AFI (is used in inventory) */ uint16_t UserLockBits_Mask = 0; /* Holds lock state of blocks */ uint16_t FactoryLockBits_Mask = 0; /* Holds lock state of blocks */ -CurrentFrame FrameInfo; void TITagitstandardAppInit(void) { State = STATE_READY; @@ -35,6 +33,7 @@ void TITagitstandardAppInit(void) { FrameInfo.Selected = false; MemoryReadBlock(&MyAFI, TITAGIT_MEM_AFI_ADDRESS, 1); + TITagitstandardGetUid(Uid); } void TITagitstandardAppReset(void) { @@ -46,6 +45,9 @@ void TITagitstandardAppReset(void) { FrameInfo.ParamLen = 0; FrameInfo.Addressed = false; FrameInfo.Selected = false; + + MemoryReadBlock(&MyAFI, TITAGIT_MEM_AFI_ADDRESS, 1); + TITagitstandardGetUid(Uid); } @@ -58,9 +60,7 @@ void TITagitstandardAppTick(void) { } uint16_t TITagitstandardAppProcess(uint8_t *FrameBuf, uint16_t FrameBytes) { - uint16_t ResponseByteCount = ISO15693_APP_NO_RESPONSE; - uint8_t Uid[ActiveConfiguration.UidSize]; - TITagitstandardGetUid(Uid); + ResponseByteCount = ISO15693_APP_NO_RESPONSE; if ((FrameBytes < ISO15693_MIN_FRAME_SIZE) || !ISO15693CheckCRC(FrameBuf, FrameBytes - ISO15693_CRC16_SIZE)) /* malformed frame */ @@ -198,12 +198,6 @@ uint16_t TITagitstandardAppProcess(uint8_t *FrameBuf, uint16_t FrameBytes) { break; } - if (ResponseByteCount > 0) { - /* There is data to be sent. Append CRC */ - ISO15693AppendCRC(FrameBuf, ResponseByteCount); - ResponseByteCount += ISO15693_CRC16_SIZE; - } - return ResponseByteCount; } @@ -214,7 +208,8 @@ void TITagitstandardGetUid(ConfigurationUidType Uid) { TITagitstandardFlipUid(Uid); } -void TITagitstandardSetUid(ConfigurationUidType Uid) { +void TITagitstandardSetUid(ConfigurationUidType NewUid) { + memcpy(Uid, NewUid, ActiveConfiguration.UidSize); // Update the local variable // Reverse UID before writing it TITagitstandardFlipUid(Uid); diff --git a/Firmware/Chameleon-Mini/Application/Vicinity.c b/Firmware/Chameleon-Mini/Application/Vicinity.c index a79e167..c3bc171 100644 --- a/Firmware/Chameleon-Mini/Application/Vicinity.c +++ b/Firmware/Chameleon-Mini/Application/Vicinity.c @@ -92,12 +92,6 @@ uint16_t VicinityAppProcess(uint8_t *FrameBuf, uint16_t FrameBytes) { break; } - if (ResponseByteCount > 0) { - /* There is data to be sent. Append CRC */ - ISO15693AppendCRC(FrameBuf, ResponseByteCount); - ResponseByteCount += ISO15693_CRC16_SIZE; - } - return ResponseByteCount; } else { // Invalid CRC diff --git a/Firmware/Chameleon-Mini/Button.c b/Firmware/Chameleon-Mini/Button.c index 392cdc0..5d57253 100644 --- a/Firmware/Chameleon-Mini/Button.c +++ b/Firmware/Chameleon-Mini/Button.c @@ -43,6 +43,7 @@ static const MapEntryType PROGMEM ButtonActionMap[] = { { .Id = BUTTON_ACTION_STORE_LOG, .Text = "STORE_LOG" }, { .Id = BUTTON_ACTION_CLEAR_LOG, .Text = "CLEAR_LOG" }, { .Id = BUTTON_ACTION_CLONE, .Text = "CLONE" }, + { .Id = BUTTON_ACTION_CLONE_MFU, .Text = "CLONE_MFU" }, }; static void ExecuteButtonAction(ButtonActionEnum ButtonAction) { @@ -202,6 +203,10 @@ static void ExecuteButtonAction(ButtonActionEnum ButtonAction) { CommandExecute("CLONE"); break; } + case BUTTON_ACTION_CLONE_MFU: { + CommandExecute("CLONE_MFU"); + break; + } default: break; diff --git a/Firmware/Chameleon-Mini/Button.h b/Firmware/Chameleon-Mini/Button.h index 4ec4833..dd66cf9 100644 --- a/Firmware/Chameleon-Mini/Button.h +++ b/Firmware/Chameleon-Mini/Button.h @@ -35,6 +35,7 @@ typedef enum { BUTTON_ACTION_STORE_LOG, BUTTON_ACTION_CLEAR_LOG, BUTTON_ACTION_CLONE, + BUTTON_ACTION_CLONE_MFU, /* This has to be last element */ BUTTON_ACTION_COUNT diff --git a/Firmware/Chameleon-Mini/Codec/Codec.c b/Firmware/Chameleon-Mini/Codec/Codec.c index 14d0bdf..194d90d 100644 --- a/Firmware/Chameleon-Mini/Codec/Codec.c +++ b/Firmware/Chameleon-Mini/Codec/Codec.c @@ -11,7 +11,7 @@ uint16_t Reader_FWT = ISO14443A_RX_PENDING_TIMEOUT; -// 这个是从开启到准备好的时间间隔,之前不能发送数据 +// This is the time interval from start to ready, data cannot be sent before #define READER_FIELD_MINIMUM_WAITING_TIME 70 // ms static uint16_t ReaderFieldStartTimestamp = 0; @@ -25,6 +25,17 @@ static volatile struct { uint8_t CodecBuffer[CODEC_BUFFER_SIZE]; uint8_t CodecBuffer2[CODEC_BUFFER_SIZE]; + +enum RCTraffic SniffTrafficSource; + +void (* volatile isr_func_TCD0_CCC_vect)(void) = NULL; +void (* volatile isr_func_CODEC_DEMOD_IN_INT0_VECT)(void) = NULL; +void (* volatile isr_func_ACA_AC0_vect)(void); +void (* volatile isr_func_CODEC_TIMER_LOADMOD_OVF_VECT)(void) = NULL; +void (* volatile isr_func_CODEC_TIMER_LOADMOD_CCA_VECT)(void) = NULL; +void (* volatile isr_func_CODEC_TIMER_LOADMOD_CCB_VECT)(void) = NULL; +void (* volatile isr_func_CODEC_TIMER_TIMESTAMPS_CCA_VECT)(void) = NULL; + // the following three functions prevent sending data directly after turning on the reader field void CodecReaderFieldStart(void) { // DO NOT CALL THIS FUNCTION INSIDE APPLICATION! if (!CodecGetReaderField() && !ReaderFieldFlags.ToBeRestarted) { diff --git a/Firmware/Chameleon-Mini/Codec/Codec.h b/Firmware/Chameleon-Mini/Codec/Codec.h index d985e42..0005d8c 100644 --- a/Firmware/Chameleon-Mini/Codec/Codec.h +++ b/Firmware/Chameleon-Mini/Codec/Codec.h @@ -8,26 +8,6 @@ #ifndef CODEC_H_ #define CODEC_H_ -#include -#include -#include -#include "../Common.h" -#include "../Configuration.h" -#include "../Settings.h" - -#include "ISO14443-2A.h" -#include "Reader14443-2A.h" -#include "SniffISO14443-2A.h" -#include "ISO15693.h" - -/* Timing definitions for ISO14443A */ -#define ISO14443A_SUBCARRIER_DIVIDER 16 -#define ISO14443A_BIT_GRID_CYCLES 128 -#define ISO14443A_BIT_RATE_CYCLES 128 -#define ISO14443A_FRAME_DELAY_PREV1 1236 -#define ISO14443A_FRAME_DELAY_PREV0 1172 -#define ISO14443A_RX_PENDING_TIMEOUT 4 // ms - /* Peripheral definitions */ #define CODEC_DEMOD_POWER_PORT PORTB #define CODEC_DEMOD_POWER_MASK PIN0_bm @@ -58,6 +38,7 @@ #define CODEC_SUBCARRIER_CCEN_PSK TC1_CCAEN_bm #define CODEC_SUBCARRIER_CCEN_OOK TC1_CCBEN_bm #define CODEC_TIMER_SAMPLING TCD0 +#define CODEC_TIMER_SAMPLING_OVF_VECT TCD0_OVF_vect #define CODEC_TIMER_SAMPLING_CCA_VECT TCD0_CCA_vect #define CODEC_TIMER_SAMPLING_CCB_VECT TCD0_CCB_vect #define CODEC_TIMER_SAMPLING_CCC_VECT TCD0_CCC_vect @@ -84,9 +65,32 @@ #define CODEC_THRESHOLD_CALIBRATE_MAX 2048 #define CODEC_THRESHOLD_CALIBRATE_STEPS 16 #define CODEC_TIMER_TIMESTAMPS TCD1 +#define CODEC_TIMER_TIMESTAMPS_OVF_VECT TCD1_OVF_vect #define CODEC_TIMER_TIMESTAMPS_CCA_VECT TCD1_CCA_vect #define CODEC_TIMER_TIMESTAMPS_CCB_VECT TCD1_CCB_vect +#ifndef __ASSEMBLER__ + +#include +#include +#include +#include "../Common.h" +#include "../Configuration.h" +#include "../Settings.h" + +#include "ISO14443-2A.h" +#include "Reader14443-2A.h" +#include "SniffISO14443-2A.h" +#include "ISO15693.h" +#include "SniffISO15693.h" + +/* Timing definitions for ISO14443A */ +#define ISO14443A_SUBCARRIER_DIVIDER 16 +#define ISO14443A_BIT_GRID_CYCLES 128 +#define ISO14443A_BIT_RATE_CYCLES 128 +#define ISO14443A_FRAME_DELAY_PREV1 1236 +#define ISO14443A_FRAME_DELAY_PREV0 1172 +#define ISO14443A_RX_PENDING_TIMEOUT 4 // ms #define CODEC_BUFFER_SIZE 256 @@ -115,15 +119,31 @@ typedef enum { extern uint8_t CodecBuffer[CODEC_BUFFER_SIZE]; extern uint8_t CodecBuffer2[CODEC_BUFFER_SIZE]; -void (* volatile isr_func_TCD0_CCC_vect)(void); +extern enum RCTraffic {TRAFFIC_READER, TRAFFIC_CARD} SniffTrafficSource; + +/* Shared ISR pointers and handlers */ +extern void (* volatile isr_func_TCD0_CCC_vect)(void); void isr_Reader14443_2A_TCD0_CCC_vect(void); void isr_ISO15693_CODEC_TIMER_SAMPLING_CCC_VECT(void); -void (* volatile isr_func_CODEC_DEMOD_IN_INT0_VECT)(void); +extern void (* volatile isr_func_CODEC_DEMOD_IN_INT0_VECT)(void); void isr_ISO14443_2A_TCD0_CCC_vect(void); void isr_ISO15693_CODEC_DEMOD_IN_INT0_VECT(void); -void (* volatile isr_func_CODEC_TIMER_LOADMOD_CCB_VECT)(void); +extern void (* volatile isr_func_CODEC_TIMER_LOADMOD_OVF_VECT)(void); +void isr_ISO14443_2A_CODEC_TIMER_LOADMOD_OVF_VECT(void); +void isr_SNIFF_ISO15693_CODEC_TIMER_LOADMOD_OVF_VECT(void); +extern void (* volatile isr_func_CODEC_TIMER_LOADMOD_CCA_VECT)(void); +void isr_Reader14443_2A_CODEC_TIMER_LOADMOD_CCA_VECT(void); +void isr_SNIFF_ISO15693_CODEC_TIMER_LOADMOD_CCA_VECT(void); +extern void (* volatile isr_func_CODEC_TIMER_LOADMOD_CCB_VECT)(void); void isr_ISO15693_CODEC_TIMER_LOADMOD_CCB_VECT(void); void isr_SniffISO14443_2A_CODEC_TIMER_LOADMOD_CCB_VECT(void); +void isr_SNIFF_ISO15693_CODEC_TIMER_LOADMOD_CCA_VECT(void); +extern void (* volatile isr_func_CODEC_TIMER_TIMESTAMPS_CCA_VECT)(void); +void isr_Reader14443_2A_CODEC_TIMER_TIMESTAMPS_CCA_VECT(void); +void isr_SNIFF_ISO15693_CODEC_TIMER_TIMESTAMPS_CCA_VECT(void); +extern void (* volatile isr_func_ACA_AC0_vect)(void); +void isr_SniffISO14443_2A_ACA_AC0_VECT(void); +void isr_SNIFF_ISO15693_ACA_AC0_VECT(void); INLINE void CodecInit(void) { ActiveConfiguration.CodecInitFunc(); @@ -164,9 +184,6 @@ INLINE void CodecInitCommon(void) { EVSYS.CH0MUX = CODEC_DEMOD_IN_EVMUX0; EVSYS.CH1MUX = CODEC_DEMOD_IN_EVMUX1; - EVSYS.CH2MUX = CODEC_DEMOD_IN_EVMUX0; - - /* Configure loadmod pin configuration and use a virtual port configuration * for single instruction cycle access */ CODEC_LOADMOD_PORT.DIRSET = CODEC_LOADMOD_MASK; @@ -285,4 +302,7 @@ bool CodecIsReaderToBeRestarted(void); void CodecThresholdSet(uint16_t th); uint16_t CodecThresholdIncrement(void); void CodecThresholdReset(void); + +#endif /* __ASSEMBLER__ */ + #endif /* CODEC_H_ */ diff --git a/Firmware/Chameleon-Mini/Codec/ISO14443-2A.c b/Firmware/Chameleon-Mini/Codec/ISO14443-2A.c index ddad5ce..6226752 100644 --- a/Firmware/Chameleon-Mini/Codec/ISO14443-2A.c +++ b/Firmware/Chameleon-Mini/Codec/ISO14443-2A.c @@ -79,13 +79,8 @@ static void StartDemod(void) { CODEC_DEMOD_IN_PORT.INT0MASK = CODEC_DEMOD_IN_MASK0; } -ISR(CODEC_DEMOD_IN_INT0_VECT) { - isr_func_CODEC_DEMOD_IN_INT0_VECT(); -} - -// ISR(CODEC_DEMOD_IN_INT0_VECT) // Find first pause and start sampling -void isr_ISO14443_2A_TCD0_CCC_vect(void) { +ISR_SHARED isr_ISO14443_2A_TCD0_CCC_vect(void) { /* 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. */ @@ -228,7 +223,7 @@ ISR(CODEC_TIMER_SAMPLING_CCA_VECT) { } // Enumulate as a card to send card responds -ISR(CODEC_TIMER_LOADMOD_OVF_VECT) { +ISR_SHARED isr_ISO14443_2A_CODEC_TIMER_LOADMOD_OVF_VECT(void) { /* Bit rate timer. Output a half bit on the output. */ static void *JumpTable[] = { @@ -391,6 +386,7 @@ void ISO14443ACodecInit(void) { isr_func_TCD0_CCC_vect = &isr_Reader14443_2A_TCD0_CCC_vect; isr_func_CODEC_DEMOD_IN_INT0_VECT = &isr_ISO14443_2A_TCD0_CCC_vect; + isr_func_CODEC_TIMER_LOADMOD_OVF_VECT = &isr_ISO14443_2A_CODEC_TIMER_LOADMOD_OVF_VECT; CodecInitCommon(); StartDemod(); } diff --git a/Firmware/Chameleon-Mini/Codec/ISO15693.c b/Firmware/Chameleon-Mini/Codec/ISO15693.c index 152837e..0221484 100644 --- a/Firmware/Chameleon-Mini/Codec/ISO15693.c +++ b/Firmware/Chameleon-Mini/Codec/ISO15693.c @@ -4,6 +4,25 @@ * Created on: 25.01.2017 * Author: Phillip Nash * Modified by: ceres-c + * + * Interrupts information + * - Compare/Capture Channels are used as compare. They're used to sample the field in different moments of time + * - Reader field demodulation events are routed on Event Channel 0, card data modulation on Event Channel 6 + * - Reader field demodulation uses Compare Channel C, card data modulation Compare Channel D (PORTB in both cases, of course) + * + * Loadmod code flow: + * Loadmodulation is performed via a state machine inside a ISR, triggered by a counter's (TCD0) overflow interrupt. The interrupt + * is configured in StartISO15693Demod to have a PER (period) equal to ISO15693 t1 nominal time: 4352 carrier pulses. + * (see ISO15693-3:2009, section 9.1). + * Once a EOF is reached when demodulating data from the reader, TCD0's PERBUF register is configured with the period of + * bit transmission (32 carrier pulses). This means that, once the first period overflow is reached (t1 expiration) + * the ISR will output data at the frequency dictated by the standard. This is due to the behaviour of buffered registers, see + * 8045A-AVR-02/08 section 3.7. + * + * Once t1 is reached (the counter overflowed), transmission of data should theoretically begin, but this is not guaranteed, + * due to possible slowdowns of data generation in the currently running Application. Until the application is done, + * the state machine will be stuck in LOADMOD_WAIT state, not outputting any data. + * The ISR will now be invoked every 32 carrier pulses (see ISO15693-2:2006, section 8.2), even when waiting for data. */ #include "ISO15693.h" @@ -12,18 +31,20 @@ #include "LEDHook.h" #include "AntennaLevel.h" #include "Terminal/Terminal.h" -//#include -#include #include #define SOC_1_OF_4_CODE 0x7B #define SOC_1_OF_256_CODE 0x7E #define EOC_CODE 0xDF +#define REQ_SUBCARRIER_SINGLE 0x00 +#define REQ_SUBCARRIER_DUAL 0x01 +#define REQ_DATARATE_LOW 0x00 +#define REQ_DATARATE_HIGH 0x02 #define ISO15693_SAMPLE_CLK TC_CLKSEL_DIV2_gc // 13.56MHz #define ISO15693_SAMPLE_PERIOD 128 // 9.4us +#define ISO15693_T1_TIME 4352 - 14 /* ISO t1 time - ISR prologue compensation */ // 4192 + 128 + 128 - 1 -#define WRITE_GRID_CYCLES 4096 #define SUBCARRIER_1 32 #define SUBCARRIER_2 28 #define SUBCARRIER_OFF 0 @@ -43,7 +64,6 @@ static volatile struct { volatile bool DemodFinished; volatile bool LoadmodFinished; - volatile bool DemodAborted; } Flags = { 0 }; typedef enum { @@ -78,26 +98,22 @@ static volatile uint8_t ByteCount; static volatile uint16_t BitRate1; static volatile uint16_t BitRate2; static volatile uint16_t SampleDataCount; -static volatile uint16_t ReadCommandFromReader = 0; /* This function implements CODEC_DEMOD_IN_INT0_VECT interrupt vector. * It is called when a pulse is detected in CODEC_DEMOD_IN_PORT (PORTB). - * The relevatn interrupt vector is registered to CODEC_DEMOD_IN_MASK0 (PIN1) via: + * The relevant interrupt vector was registered to CODEC_DEMOD_IN_MASK0 (PIN1) via: * CODEC_DEMOD_IN_PORT.INT0MASK = CODEC_DEMOD_IN_MASK0; * and unregistered writing the INT0MASK to 0 */ -// ISR(CODEC_DEMOD_IN_INT0_VECT) -void isr_ISO15693_CODEC_DEMOD_IN_INT0_VECT(void) { - /* Start sample timer CODEC_TIMER_SAMPLING (TCD0). - * Set Counter Channel C (CCC) with relevant bitmask (TC0_CCCIF_bm), - * the period for clock sampling is specified in StartISO15693Demod. - */ +ISR_SHARED isr_ISO15693_CODEC_DEMOD_IN_INT0_VECT(void) { + /* Clear Compare Channel C (CCC) interrupt Flags - From 14.12.10 [8331F–AVR–04/2013] */ CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCCIF_bm; - /* Sets register INTCTRLB to TC_CCCINTLVL_HI_gc = (0x03<<4) to enable compare/capture for high level interrupts on Channel C (CCC) */ + /* Enable compare/capture for high level interrupts on Capture Channel C for TCD0 - From 14.12.7 [8331F–AVR–04/2013] */ CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCCINTLVL_HI_gc; - /* Disable this interrupt as we've already sensed the relevant pulse and will use our internal clock from now on */ + /* Disable this interrupt. From now on we will sample the field using our internal clock - From 13.13.11 [8331F–AVR–04/2013] */ CODEC_DEMOD_IN_PORT.INT0MASK = 0; + } /* This function is called from isr_ISO15693_CODEC_TIMER_SAMPLING_CCC_VECT @@ -105,36 +121,50 @@ void isr_ISO15693_CODEC_DEMOD_IN_INT0_VECT(void) { */ INLINE void ISO15693_EOC(void) { /* Set bitrate required by the reader on SOF for our following response */ - BitRate1 = 256 * 4; // 256 * 4 - 1 - if (CodecBuffer[0] & ISO15693_REQ_DATARATE_HIGH) + if (CodecBuffer[0] & REQ_DATARATE_HIGH) { BitRate1 = 256; - - if (CodecBuffer[0] & ISO15693_REQ_SUBCARRIER_DUAL) { - BitRate2 = 252 * 4; // 252 * 4 - 3 - if (CodecBuffer[0] & ISO15693_REQ_DATARATE_HIGH) - BitRate2 = 252; + BitRate2 = 252; /* If single subcarrier mode is requested, BitRate2 is useless, but setting it nevertheless was still faster than checking */ } else { - BitRate2 = BitRate1; + BitRate1 = 256 * 4; // Possible value: 256 * 4 - 1 + BitRate2 = 252 * 4; // Possible value: 252 * 4 - 3 } - /* Disable event action for CODEC_TIMER_LOADMOD (TCE0) as we're done receiving data */ - CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_OFF_gc; - /* Set Counter Channel B (CCB) with relevant bitmask (TC0_CCBIF_bm), the period for clock sampling is specified below */ - CODEC_TIMER_LOADMOD.INTFLAGS = TC0_CCBIF_bm; // TODO This might not be needed since commenting it does not break anything - /* Sets register INTCTRLB to TC_CCBINTLVL_HI_gc = (0x03<<2) to enable compare/capture for high level interrupts on channel B */ - CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCBINTLVL_HI_gc; - /* Set the period for CODEC_TIMER_LOADMOD (TCE0) to Bitrate - 1 because PERBUF is 0-based - * - * TODO Why are we using PERBUF instead of PER? - * With PERBUF the period register will occur on the next overflow. - */ - CODEC_TIMER_LOADMOD.PERBUF = BitRate1 - 1; - Flags.DemodFinished = 1; - /* Sets timer off for CODEC_TIMER_SAMPLING (TCD0) disabling clock source */ + /* Disable demodulation interrupt */ + /* Sets timer off for TCD0, disabling clock source. We're done receiving data from reader and don't need to probe the antenna anymore - From 14.12.1 [8331F–AVR–04/2013] */ CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_OFF_gc; - /* Sets register INTCTRLB to 0 to disable all compare/capture interrupts */ - CODEC_TIMER_SAMPLING.INTCTRLB = 0; + /* Disable event action for CODEC_TIMER_SAMPLING (TCD0) - From 14.12.4 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.CTRLD = TC_EVACT_OFF_gc; + /* Disable compare/capture interrupts on Channel C - From 14.12.7 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCCINTLVL_OFF_gc; + /* Clear Compare Channel C (CCC) interrupt Flags - From 14.12.10 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCCIF_bm; + + /* Enable loadmodulation interrupt */ + /* Disable the event action for TCE0 - From 14.12.4 [8331F–AVR–04/2013] */ + CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_OFF_gc; + /* Enable compare/capture for high level interrupts on channel B for TCE0 - From 14.12.7 [8331F–AVR–04/2013] */ + CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCBINTLVL_HI_gc; + /* Clear TCE0 interrupt flags for Capture Channel B - From 14.12.10 [8331F–AVR–04/2013] */ + CODEC_TIMER_LOADMOD.INTFLAGS = TC0_CCBIF_bm; + /* Set the BUFFERED PERIOD to Bitrate - 1 (because PERBUF is 0-based). + * The current PERIOD was set in StartISO15693Demod to ISO15693_T1_TIME, once ISO15693_T1_TIME is reached, this will be the new PERIOD. + * From 3.7 [8045A-AVR-02/08] */ + CODEC_TIMER_LOADMOD.PERBUF = BitRate1 - 1; +} + +/* Disable data demodulation interrupt and inform the codec to restart demodulation from scratch */ +INLINE void ISO15693_GARBAGE(void) { + Flags.DemodFinished = 1; + + /* Sets timer off for TCD0, disabling clock source. We have demodulated rubbish and don't need to probe the antenna anymore - From 14.12.1 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_OFF_gc; + /* Disable event action for CODEC_TIMER_SAMPLING (TCD0) - From 14.12.4 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.CTRLD = TC_EVACT_OFF_gc; + /* Disable compare/capture interrupts on Channel C - From 14.12.7 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCCINTLVL_OFF_gc; + /* Clear Compare Channel C (CCC) interrupt Flags - From 14.12.10 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCCIF_bm; } /* This function is registered to CODEC_TIMER_SAMPLING (TCD0)'s Counter Channel C (CCC). @@ -142,10 +172,9 @@ INLINE void ISO15693_EOC(void) { * * It demodulates bits received from the reader and saves them in CodecBuffer. * - * It disables its own interrupt when receives an EOF (calling ISO15693_EOC) or when it receives garbage + * It disables its own interrupt when an EOF is found (calling ISO15693_EOC) or when it receives garbage */ -// ISR(CODEC_TIMER_SAMPLING_CCC_VECT) // Reading data sent from the reader -void isr_ISO15693_CODEC_TIMER_SAMPLING_CCC_VECT(void) { +ISR_SHARED isr_ISO15693_CODEC_TIMER_SAMPLING_CCC_VECT(void) { /* Shift demod data */ SampleRegister = (SampleRegister << 1) | (!(CODEC_DEMOD_IN_PORT.IN & CODEC_DEMOD_IN_MASK) ? 0x01 : 0x00); @@ -161,11 +190,7 @@ void isr_ISO15693_CODEC_TIMER_SAMPLING_CCC_VECT(void) { DemodState = DEMOD_1_OUT_OF_256_STATE; SampleDataCount = 0; } else { // No SOC. Restart and try again, we probably received garbage. - Flags.DemodFinished = 1; - /* Sets timer off for CODEC_TIMER_SAMPLING (TCD0) disabling clock source */ - CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_OFF_gc; - /* Sets register INTCTRLB to 0 to disable all compare/capture interrupts */ - CODEC_TIMER_SAMPLING.INTCTRLB = 0; + ISO15693_GARBAGE(); } break; @@ -248,7 +273,6 @@ void isr_ISO15693_CODEC_TIMER_SAMPLING_CCC_VECT(void) { SampleDataCount++; } - /* This function is registered to CODEC_TIMER_LOADMOD (TCE0)'s Counter Channel B (CCB). * When the timer is enabled, this is called on counter's overflow * @@ -256,9 +280,9 @@ void isr_ISO15693_CODEC_TIMER_SAMPLING_CCC_VECT(void) { * * It disables its own interrupt when all data has been sent */ -//ISR(CODEC_TIMER_LOADMOD_CCB_VECT) -void isr_ISO15693_CODEC_TIMER_LOADMOD_CCB_VECT(void) { +ISR_SHARED isr_ISO15693_CODEC_TIMER_LOADMOD_CCB_VECT(void) { static void *JumpTable[] = { + [LOADMOD_WAIT] = && LOADMOD_WAIT_LABEL, [LOADMOD_START_SINGLE] = && LOADMOD_START_SINGLE_LABEL, [LOADMOD_SOF_SINGLE] = && LOADMOD_SOF_SINGLE_LABEL, [LOADMOD_BIT0_SINGLE] = && LOADMOD_BIT0_SINGLE_LABEL, @@ -272,11 +296,11 @@ void isr_ISO15693_CODEC_TIMER_LOADMOD_CCB_VECT(void) { [LOADMOD_FINISHED] = && LOADMOD_FINISHED_LABEL }; - if ((StateRegister >= LOADMOD_START_SINGLE) && (StateRegister <= LOADMOD_FINISHED)) { - goto *JumpTable[StateRegister]; - } else { - return; - } + goto *JumpTable[StateRegister]; /* It takes 28 clock cycles to get here from function entry point */ + +LOADMOD_WAIT_LABEL: + /* ISO t1 is over, but application is not done generating data yet */ + return; LOADMOD_START_SINGLE_LABEL: /* Application produced data. With this interrupt we are aligned to the bit-grid. */ @@ -354,9 +378,7 @@ LOADMOD_EOF_SINGLE_LABEL: //End of Manchester encoding if ((BitSent % 8) == 0) { /* Last bit has been put out */ StateRegister = LOADMOD_FINISHED; - } else { - StateRegister = LOADMOD_EOF_SINGLE; - } + } /* else: stay in this state. No changes needed */ return; // ------------------------------------------------------------- @@ -444,22 +466,20 @@ LOADMOD_EOF_DUAL_LABEL: //End of Manchester encoding if ((BitSent % 8) == 0) { /* Last bit has been put out */ StateRegister = LOADMOD_FINISHED; - } else { - StateRegister = LOADMOD_EOF_DUAL; - } + } /* else: stay in this state. No changes needed */ return; LOADMOD_FINISHED_LABEL: /* Sets timer off for CODEC_TIMER_LOADMOD (TCE0) disabling clock source as we're done modulating */ CODEC_TIMER_LOADMOD.CTRLA = TC_CLKSEL_OFF_gc; - /* Sets register INTCTRLB to 0 to disable all compare/capture interrupts */ - CODEC_TIMER_LOADMOD.INTCTRLB = 0; + /* Disable compare/capture for all level interrupts on channel B for TCE0 - From 14.12.7 [8331F–AVR–04/2013] */ + CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCBINTLVL_OFF_gc; CodecSetSubcarrier(CODEC_SUBCARRIERMOD_OFF, 0); Flags.LoadmodFinished = 1; return; } -/* This functions resets all global variables used in the codec and enables interrupts to wait for reader data */ +/* Reset global variables/interrupts to demodulate incoming reader data via ISRs */ void StartISO15693Demod(void) { /* Reset global variables to default values */ CodecBufferPtr = CodecBuffer; @@ -475,69 +495,85 @@ void StartISO15693Demod(void) { ByteCount = 0; ShiftRegister = 0; - /* Activate Power for demodulator */ - CodecSetDemodPower(true); - - /* Configure sampling-timer free running and sync to first modulation-pause. */ - /* Resets the counter to 0 */ - CODEC_TIMER_SAMPLING.CNT = 0; - /* Set the period for CODEC_TIMER_SAMPLING (TCD0) to ISO15693_SAMPLE_PERIOD - 1 because PER is 0-based */ - CODEC_TIMER_SAMPLING.PER = ISO15693_SAMPLE_PERIOD - 1; - /* Set Counter Channel C (CCC) register with half bit period - 1. (- 14 to compensate ISR timing overhead) */ - CODEC_TIMER_SAMPLING.CCC = ISO15693_SAMPLE_PERIOD / 2 - 14 - 1; - /* Set timer for CODEC_TIMER_SAMPLING (TCD0) to ISO15693_SAMPLE_CLK = TC_CLKSEL_DIV2_gc = System Clock / 2 - * - * TODO Why system clock / 2 and not iso period? + /* Set clock source for TCD0 to ISO15693_SAMPLE_CLK = TC_CLKSEL_DIV2_gc = System Clock / 2. + * Since the Chameleon is clocked at 13.56*2 MHz (see Makefile), this counter will hit at the same frequency of reader field. + * From 14.12.1 [8331F–AVR–04/2013] */ CODEC_TIMER_SAMPLING.CTRLA = ISO15693_SAMPLE_CLK; - /* Set event action for CODEC_TIMER_SAMPLING (TCD0) to restart and trigger CODEC_TIMER_MODSTART_EVSEL = TC_EVSEL_CH0_gc = Event Channel 0 */ + /* Set up TCD0 action/source: + * - Event Action: Restart waveform period (reset PER register) + * - Event Source Select: trigger on CODEC_TIMER_MODSTART_EVSEL = TC_EVSEL_CH0_gc = Event Channel 0 + * From 14.12.4 [8331F–AVR–04/2013] + */ CODEC_TIMER_SAMPLING.CTRLD = TC_EVACT_RESTART_gc | CODEC_TIMER_MODSTART_EVSEL; - /* Set Counter Channel C (CCC) with relevant bitmask (TC0_CCCIF_bm), the period for clock sampling is specified above */ - CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCCIF_bm; - /* Sets register INTCTRLB to TC_CCCINTLVL_OFF_gc = (0x00<<4) to disable compare/capture C interrupts - * - * TODO Why turn it off? - */ - CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCCINTLVL_OFF_gc; + /* Resets the sampling counter (TCD0) to 0 - From 14.12.12 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.CNT = 0; - /* Set event action for CODEC_TIMER_LOADMOD (TCE0) to restart and trigger CODEC_TIMER_MODSTART_EVSEL = TC_EVSEL_CH0_gc = Event Channel 0 */ - CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_RESTART_gc | CODEC_TIMER_MODSTART_EVSEL; - /* Set the period for CODEC_TIMER_LOADMOD (TCE0) to... some magic numbers? - * Using PER instead of PERBUF breaks it when receiving ISO15693_APP_NO_RESPONSE from Application. - * - * TODO What are these numbers? + /* Set Clock Source for TCE0 to CODEC_TIMER_CARRIER_CLKSEL = TC_CLKSEL_EVCH6_gc = Event Channel 6 */ + CODEC_TIMER_LOADMOD.CTRLA = CODEC_TIMER_CARRIER_CLKSEL; + /* Set up TCE0 action/source: + * - Event Action: Restart waveform period (reset PER register) + * - Event Source Select: trigger on CODEC_TIMER_MODSTART_EVSEL = TC_EVSEL_CH0_gc = Event Channel 0 + * From 14.12.4 [8331F–AVR–04/2013] */ - CODEC_TIMER_LOADMOD.PERBUF = 4192 + 128 + 128 - 1; - /* Sets register INTCTRLA to 0 to disable timer error or overflow interrupts */ - CODEC_TIMER_LOADMOD.INTCTRLA = 0; - /* Sets register INTCTRLB to 0 to disable all compare/capture interrupts */ - CODEC_TIMER_LOADMOD.INTCTRLB = 0; - /* Set timer for CODEC_TIMER_SAMPLING (TCD0) to TC_CLKSEL_EVCH6_gc = Event Channel 6 */ - CODEC_TIMER_LOADMOD.CTRLA = TC_CLKSEL_EVCH6_gc; + CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_RESTART_gc | CODEC_TIMER_MODSTART_EVSEL; + /* Temporarily disable compare/capture for all level interrupts on channel B for TCE0. They'll be enabled later when load modulation will be needed. + * From 14.12.7 [8331F–AVR–04/2013] */ + CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCBINTLVL_OFF_gc; + /* Set the period for CODEC_TIMER_LOADMOD (TCE0) ISO standard t1 time. + * Once this timer is enabled, it will wait for a time equal to t1 and then its ISR will be called. + * From 14.12.18 [8331F–AVR–04/2013] + */ + CODEC_TIMER_LOADMOD.PER = ISO15693_T1_TIME; /* Start looking out for modulation pause via interrupt. */ - /* Sets register INTFLAGS to PORT_INT0LVL_HI_gc = (0x03<<0) to enable compare/capture for high level interrupts on CODEC_DEMOD_IN_PORT (PORTB) */ - CODEC_DEMOD_IN_PORT.INTFLAGS = PORT_INT0LVL_HI_gc; - /* Sets INT0MASK to CODEC_DEMOD_IN_MASK0 = PIN1_bm to use it as source for port interrupt 0 */ + /* Clear PORTB interrupt flags - From 13.13.13 [8331F–AVR–04/2013] */ + CODEC_DEMOD_IN_PORT.INTFLAGS = PORT_INT0IF_bm; + /* Use PIN1 as a source for modulation pauses. Will trigger PORTB Interrput 0 - From 13.13.11 [8331F–AVR–04/2013] */ CODEC_DEMOD_IN_PORT.INT0MASK = CODEC_DEMOD_IN_MASK0; } +/* Configure unvarying interrupts settings. + * All configured interrupts will still be disabled once this function is done, they'll be enabled one by one as the communication flow requires them. + */ void ISO15693CodecInit(void) { CodecInitCommon(); - /* Register isr_ISO15693_CODEC_TIMER_SAMPLING_CCC_VECT function - * to CODEC_TIMER_SAMPLING (TCD0)'s Counter Channel C (CCC) - */ + /************************************************** + * Register function handlers to shared ISR * + **************************************************/ + /* Register isr_ISO15693_CODEC_TIMER_SAMPLING_CCC_VECT function to CODEC_TIMER_SAMPLING (TCD0)'s Counter Channel C (CCC) */ isr_func_TCD0_CCC_vect = &isr_ISO15693_CODEC_TIMER_SAMPLING_CCC_VECT; - /* Register isr_ISO15693_CODEC_DEMOD_IN_INT0_VECT function - * to CODEC_DEMOD_IN_PORT (PORTB) interrupt 0 - */ + /* Register isr_ISO15693_CODEC_DEMOD_IN_INT0_VECT function to CODEC_DEMOD_IN_PORT (PORTB) interrupt 0 */ isr_func_CODEC_DEMOD_IN_INT0_VECT = &isr_ISO15693_CODEC_DEMOD_IN_INT0_VECT; - /* Register isr_ISO15693_CODEC_TIMER_LOADMOD_CCB_VECT function - * to CODEC_TIMER_LOADMOD (TCE0)'s Counter Channel B (CCB) - */ + /* Register isr_ISO15693_CODEC_TIMER_LOADMOD_CCB_VECT function to CODEC_TIMER_LOADMOD (TCE0)'s Counter Channel B (CCB) */ isr_func_CODEC_TIMER_LOADMOD_CCB_VECT = &isr_ISO15693_CODEC_TIMER_LOADMOD_CCB_VECT; + /************************************************** + * Configure demod interrupt * + **************************************************/ + /* Configure sampling-timer free running and sync to first modulation-pause */ + /* Set the period for TCD0 to ISO15693_SAMPLE_PERIOD - 1 because PER is 0-based - From 14.12.14 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.PER = ISO15693_SAMPLE_PERIOD - 1; + /* Set Compare Channel C (CCC) period to half bit period - 1. (- 14 to compensate ISR timing overhead) - From 14.12.16 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.CCC = ISO15693_SAMPLE_PERIOD / 2 - 14 - 1; + /* Temporarily disable Compare Channel C (CCC) interrupts. + * They'll be enabled later in isr_ISO15693_CODEC_DEMOD_IN_INT0_VECT once we sensed the reader is sending data and we're in sync with the pulses. + * From 14.12.7 [8331F–AVR–04/2013] + */ + CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCCINTLVL_OFF_gc; + /* Clear Compare Channel C (CCC) interrupt Flags - From 14.12.10 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCCIF_bm; + + /************************************************** + * Configure loadmod interrupt * + **************************************************/ + /* Disable timer error and overflow interrupts - From 14.12.6 [8331F–AVR–04/2013] */ + CODEC_TIMER_LOADMOD.INTCTRLA = 0; + + /* Activate Power for demodulator */ + CodecSetDemodPower(true); + StartISO15693Demod(); } @@ -560,21 +596,21 @@ void ISO15693CodecDeInit(void) { ShiftRegister = 0; /* Disable sample timer */ - /* Sets timer off for CODEC_TIMER_SAMPLING (TCD0) disabling clock source */ + /* Sets timer off for TCD0, disabling clock source - From 14.12.1 [8331F–AVR–04/2013] */ CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_OFF_gc; - /* Disable event action for CODEC_TIMER_SAMPLING (TCD0) */ + /* Disable event action for CODEC_TIMER_SAMPLING (TCD0) - From 14.12.4 [8331F–AVR–04/2013] */ CODEC_TIMER_SAMPLING.CTRLD = TC_EVACT_OFF_gc; - /* Sets register INTCTRLB to TC_CCCINTLVL_OFF_gc = (0x00<<4) to disable compare/capture C interrupts */ + /* Disable Compare Channel C (CCC) interrupts - From 14.12.7 [8331F–AVR–04/2013] */ CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCCINTLVL_OFF_gc; - /* Restore Counter Channel C (CCC) interrupt mask (TC0_CCCIF_bm) */ + /* Clear Compare Channel C (CCC) interrupt Flags - From 14.12.10 [8331F–AVR–04/2013] */ CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCCIF_bm; /* Disable load modulation */ - /* Disable event action for CODEC_TIMER_LOADMOD (TCE0) */ + /* Disable the event action for TCE0 - From 14.12.4 [8331F–AVR–04/2013] */ CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_OFF_gc; - /* Sets register INTCTRLB to TC_CCBINTLVL_OFF_gc = (0x00<<2) to disable compare/capture B interrupts */ + /* Disable compare/capture for all level interrupts on channel B for TCE0 - From 14.12.7 [8331F–AVR–04/2013] */ CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCBINTLVL_OFF_gc; - /* Restore Counter Channel B (CCB) interrupt mask (TC0_CCBIF_bm) */ + /* Clear TCE0 interrupt flags for Capture Channel B - From 14.12.10 [8331F–AVR–04/2013] */ CODEC_TIMER_LOADMOD.INTFLAGS = TC0_CCBIF_bm; CodecSetSubcarrier(CODEC_SUBCARRIERMOD_OFF, 0); @@ -594,7 +630,7 @@ void ISO15693CodecTask(void) { LogEntry(LOG_INFO_CODEC_RX_DATA, CodecBuffer, DemodByteCount); LEDHook(LED_CODEC_RX, LED_PULSE); - if (CodecBuffer[0] & ISO15693_REQ_SUBCARRIER_DUAL) { + if (CodecBuffer[0] & REQ_SUBCARRIER_DUAL) { bDualSubcarrier = true; } AppReceivedByteCount = ApplicationProcess(CodecBuffer, DemodByteCount); @@ -602,7 +638,13 @@ void ISO15693CodecTask(void) { /* This is only reached when we've received a valid frame */ if (AppReceivedByteCount != ISO15693_APP_NO_RESPONSE) { - LogEntry(LOG_INFO_CODEC_TX_DATA, CodecBuffer, AppReceivedByteCount); + if (AppReceivedByteCount > CODEC_BUFFER_SIZE - ISO15693_CRC16_SIZE) { /* CRC would be written outside codec buffer */ + CodecBuffer[ISO15693_ADDR_FLAGS] = ISO15693_RES_FLAG_ERROR; + CodecBuffer[ISO15693_RES_ADDR_PARAM] = ISO15693_RES_ERR_NOT_SUPP; + AppReceivedByteCount = 2; + LogEntry(LOG_INFO_GENERIC, "Too much data requested - See PR #274", 38); + } + LEDHook(LED_CODEC_TX, LED_PULSE); ByteCount = AppReceivedByteCount; @@ -617,13 +659,21 @@ void ISO15693CodecTask(void) { StateRegister = LOADMOD_START_SINGLE; } - } else { - /* No data to process. Disable CODEC_TIMER_LOADMOD (TCE0) counter and start listening again */ - /* Sets timer off for CODEC_TIMER_LOADMOD (TCE0) disabling clock source as we're done modulating */ - CODEC_TIMER_LOADMOD.CTRLA = TC_CLKSEL_OFF_gc; - /* Sets register INTCTRLB to 0 to disable all compare/capture interrupts */ - CODEC_TIMER_LOADMOD.INTCTRLB = 0; + /* Calculate the CRC while modulation is already ongoing */ + ISO15693AppendCRC(CodecBuffer, AppReceivedByteCount); + ByteCount += ISO15693_CRC16_SIZE; /* Increase this variable as it will be read by the codec during loadmodulation */ + LogEntry(LOG_INFO_CODEC_TX_DATA, CodecBuffer, AppReceivedByteCount + ISO15693_CRC16_SIZE); + } else { + /* Overwrite the PERBUF register, which was configured in ISO15693_EOC, with the new appropriate value. + * This is expecially needed because, since load modulation has not been performed, we're jumping here straight after + * ISO15693_EOC. This implies that the data stored in the PERBUF register by ISO15693_EOC still has to be copied + * in the PER register and that will happen on the next UPDATE event. + * The next UPDATE event will be timer/counter enablement in ISO15693_EOC, which is executed after StartISO15693Demod, + * effectively overwriting PER with an incorrect value. + * See 8045A-AVR-02/08 section 3.7 for information about buffered registers. + */ + CODEC_TIMER_LOADMOD.PERBUF = ISO15693_T1_TIME; StartISO15693Demod(); } } diff --git a/Firmware/Chameleon-Mini/Codec/ISO15693.h b/Firmware/Chameleon-Mini/Codec/ISO15693.h index 6c2e01b..0362fdc 100644 --- a/Firmware/Chameleon-Mini/Codec/ISO15693.h +++ b/Firmware/Chameleon-Mini/Codec/ISO15693.h @@ -8,26 +8,8 @@ #ifndef ISO15693_H_ #define ISO15693_H_ -#include "Terminal/CommandLine.h" - #define ISO15693_APP_NO_RESPONSE 0x0000 -#define SUBCARRIER_1 32 -#define SUBCARRIER_2 28 -#define SUBCARRIER_OFF 0 -#define SOF_PATTERN 0x1D -#define EOF_PATTERN 0xB8 - -#define T1_NOMINAL_CYCLES 4352 - 14//4352 - 25 /* ISR prologue compensation */ -#define WRITE_GRID_CYCLES 4096 - -//Used when checking the request sent from the reader -#define ISO15693_REQ_SUBCARRIER_SINGLE 0x00 -#define ISO15693_REQ_SUBCARRIER_DUAL 0x01 -#define ISO15693_REQ_DATARATE_LOW 0x00 -#define ISO15693_REQ_DATARATE_HIGH 0x02 - - /* Codec Interface */ void ISO15693CodecInit(void); void ISO15693CodecDeInit(void); @@ -37,6 +19,4 @@ void ISO15693CodecTask(void); void ISO15693CodecStart(void); void ISO15693CodecReset(void); - - #endif /* ISO15693_H_ */ diff --git a/Firmware/Chameleon-Mini/Codec/Reader14443-2A.c b/Firmware/Chameleon-Mini/Codec/Reader14443-2A.c index d88de71..4f929eb 100644 --- a/Firmware/Chameleon-Mini/Codec/Reader14443-2A.c +++ b/Firmware/Chameleon-Mini/Codec/Reader14443-2A.c @@ -48,9 +48,10 @@ void Reader14443ACodecInit(void) { /* Initialize common peripherals and start listening * for incoming data. */ CodecInitCommon(); - - // 中断回调 + /* Register shared interrupt handlers */ isr_func_TCD0_CCC_vect = &isr_Reader14443_2A_TCD0_CCC_vect; + isr_func_CODEC_TIMER_LOADMOD_CCA_VECT = &isr_Reader14443_2A_CODEC_TIMER_LOADMOD_CCA_VECT; + isr_func_CODEC_TIMER_TIMESTAMPS_CCA_VECT = &isr_Reader14443_2A_CODEC_TIMER_TIMESTAMPS_CCA_VECT; CodecSetDemodPower(true); CODEC_TIMER_SAMPLING.PER = SAMPLE_RATE_SYSTEM_CYCLES - 1; @@ -172,13 +173,10 @@ INLINE void BufferToSequence(void) { if (BitCount % 8) CodecBuffer[BitCount / 8] = SampleRegister >> (8 - (BitCount % 8)); } -// Frame Delay Time PCD to PICC ends -ISR(CODEC_TIMER_SAMPLING_CCC_VECT) { - isr_func_TCD0_CCC_vect(); -} // ISR (TCD0_CCC_vect) -void isr_Reader14443_2A_TCD0_CCC_vect(void) { +// 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; @@ -218,7 +216,7 @@ void Reader14443AMillerEOC(void) { } // EOC of Card->Reader found -ISR(CODEC_TIMER_TIMESTAMPS_CCA_VECT) { // EOC found +ISR_SHARED isr_Reader14443_2A_CODEC_TIMER_TIMESTAMPS_CCA_VECT(void) { // EOC found Reader14443A_EOC(); } @@ -234,7 +232,8 @@ ISR(ACA_AC1_vect) { // this interrupt either finds the SOC or gets triggered bef // 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 +// ISR(CODEC_TIMER_LOADMOD_CCA_VECT) { // pause found +ISR_SHARED isr_Reader14443_2A_CODEC_TIMER_LOADMOD_CCA_VECT(void) { // pause found uint8_t tmp = CODEC_TIMER_TIMESTAMPS.CNTL; CODEC_TIMER_TIMESTAMPS.CNT = 0; diff --git a/Firmware/Chameleon-Mini/Codec/SniffISO14443-2A.c b/Firmware/Chameleon-Mini/Codec/SniffISO14443-2A.c index 801a610..2a3d683 100644 --- a/Firmware/Chameleon-Mini/Codec/SniffISO14443-2A.c +++ b/Firmware/Chameleon-Mini/Codec/SniffISO14443-2A.c @@ -345,8 +345,7 @@ INLINE void Insert1(void) { } // This interrupt find Card -> Reader SOC -ISR(ACA_AC0_vect) { // this interrupt either finds the SOC or gets triggered before - +ISR_SHARED isr_SniffISO14443_2A_ACA_AC0_VECT (void) { // this interrupt either finds the SOC or gets triggered before ACA.AC0CTRL &= ~AC_INTLVL_HI_gc; // disable this interrupt // enable the pause-finding timer CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_RESTART_gc | TC_EVSEL_CH2_gc; @@ -354,16 +353,13 @@ ISR(ACA_AC0_vect) { // this interrupt either finds the SOC or gets triggered bef StateRegister = PICC_FRAME; } -ISR(CODEC_TIMER_LOADMOD_CCB_VECT) { // pause found - isr_func_CODEC_TIMER_LOADMOD_CCB_VECT(); -} - +// Called once a pause is found // 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_CCB_VECT) // pause found -void isr_SniffISO14443_2A_CODEC_TIMER_LOADMOD_CCB_VECT(void) { +ISR_SHARED isr_SniffISO14443_2A_CODEC_TIMER_LOADMOD_CCB_VECT(void) { uint8_t tmp = CODEC_TIMER_TIMESTAMPS.CNTL; CODEC_TIMER_TIMESTAMPS.CNT = 0; @@ -481,6 +477,7 @@ void Sniff14443ACodecInit(void) { #endif // Common Codec Register settings CodecInitCommon(); + isr_func_ACA_AC0_vect = &isr_SniffISO14443_2A_ACA_AC0_VECT; isr_func_CODEC_TIMER_LOADMOD_CCB_VECT = &isr_SniffISO14443_2A_CODEC_TIMER_LOADMOD_CCB_VECT; // Enable demodulator power CodecSetDemodPower(true); @@ -511,7 +508,7 @@ void Sniff14443ACodecTask(void) { // Let the Application layer know where this data comes from LEDHook(LED_CODEC_RX, LED_PULSE); - TrafficSource = TRAFFIC_READER; + SniffTrafficSource = TRAFFIC_READER; ApplicationProcess(CodecBuffer, ReaderBitCount); } @@ -524,7 +521,7 @@ void Sniff14443ACodecTask(void) { LEDHook(LED_CODEC_RX, LED_PULSE); // Let the Application layer know where this data comes from - TrafficSource = TRAFFIC_CARD; + SniffTrafficSource = TRAFFIC_CARD; ApplicationProcess(CodecBuffer2, CardBitCount); } diff --git a/Firmware/Chameleon-Mini/Codec/SniffISO14443-2A.h b/Firmware/Chameleon-Mini/Codec/SniffISO14443-2A.h index 0e22d46..8e9080a 100644 --- a/Firmware/Chameleon-Mini/Codec/SniffISO14443-2A.h +++ b/Firmware/Chameleon-Mini/Codec/SniffISO14443-2A.h @@ -9,7 +9,6 @@ #include "Codec.h" #include "Terminal/CommandLine.h" -enum RCTraffic {TRAFFIC_READER, TRAFFIC_CARD} TrafficSource; /* Codec Interface */ void Sniff14443ACodecInit(void); void Sniff14443ACodecDeInit(void); diff --git a/Firmware/Chameleon-Mini/Codec/SniffISO15693.c b/Firmware/Chameleon-Mini/Codec/SniffISO15693.c new file mode 100644 index 0000000..2962f4e --- /dev/null +++ b/Firmware/Chameleon-Mini/Codec/SniffISO15693.c @@ -0,0 +1,767 @@ +/* + * SniffISO15693.c + * + * Created on: 25.01.2017 + * Author: ceres-c + */ + +#include "SniffISO15693.h" +#include "Codec.h" +#include "../System.h" +#include "../Application/Application.h" +#include "LEDHook.h" +#include "AntennaLevel.h" + +#define VCD_SOC_1_OF_4_CODE 0x7B +#define VCD_SOC_1_OF_256_CODE 0x7E +#define VCD_EOC_CODE 0xDF +#define VICC_SOC_CODE 0b00011101 /* = 0x1D 3 unmodulated periods, 3 modulated periods, 1 unmodulated, 1 modulated */ +#define VICC_EOC_CODE 0b10111000 /* = 0xB8 1 modulated period, 1 unmodulated period, 3 modulated, 3 unmodulated */ + +#define ISO15693_READER_SAMPLE_CLK TC_CLKSEL_DIV2_gc // 13.56MHz +#define ISO15693_READER_SAMPLE_PERIOD 128 // 9.4us + +// These registers provide quick access but are limited +// so global vars will be necessary +#define DataRegister Codec8Reg0 +#define StateRegister Codec8Reg1 +#define ModulationPauseCount Codec8Reg2 +#define BitSampleCount Codec8Reg3 /* Store the amount of received half-bits */ +#define SampleRegister CodecCount16Register1 +#define SampleRegisterH GPIOR4 /* CodecCount16Register1 is the composition of GPIOR4 and GPIOR5, divide them for easier read access */ +#define SampleRegisterL GPIOR5 +#define DemodFloorNoiseLevel CodecCount16Register2 +#define CodecBufferPtr CodecPtrRegister1 + +static volatile struct { + volatile bool ReaderDemodFinished; + volatile bool CardDemodFinished; +} Flags = { 0 }; + +typedef enum { + DEMOD_VCD_SOC_STATE, + DEMOD_VCD_1_OUT_OF_4_STATE, + DEMOD_VCD_1_OUT_OF_256_STATE, + DEMOD_VICC_SOC_STATE, + DEMOD_VICC_DATA, +} DemodSniffStateType; + +static volatile uint8_t ShiftRegister; +static volatile uint8_t ByteCount; +static volatile uint8_t ReaderByteCount; +static volatile uint8_t CardByteCount; +static volatile uint16_t SampleDataCount; + +///////////////////////////////////////////////// +// VCD->VICC +// (Code adapted from ISO15693 codec) +///////////////////////////////////////////////// + +/* This functions resets all global variables used in the codec and enables interrupts to wait for reader data */ +void ReaderSniffInit(void) { + /* Reset global variables to default values */ + CodecBufferPtr = CodecBuffer; + Flags.ReaderDemodFinished = 0; + Flags.CardDemodFinished = 0; + StateRegister = DEMOD_VCD_SOC_STATE; + DataRegister = 0; + SampleRegister = 0; + BitSampleCount = 0; + SampleDataCount = 0; + ModulationPauseCount = 0; + ByteCount = 0; + ReaderByteCount = 0; /* Clear direction-specific counters */ + CardByteCount = 0; + ShiftRegister = 0; + + /* Activate Power for demodulator */ + CodecSetDemodPower(true); + + /* Configure sampling-timer free running and sync to first modulation-pause */ + /* Set clock source for TCD0 to ISO15693_SAMPLE_CLK = TC_CLKSEL_DIV2_gc = System Clock / 2. + * Since the Chameleon is clocked at 13.56*2 MHz (see Makefile), this counter will hit at the same frequency of reader field. + * From 14.12.1 [8331F–AVR–04/2013] + */ + CODEC_TIMER_SAMPLING.CTRLA = ISO15693_READER_SAMPLE_CLK; + /* Set up TCD0 action/source: + * - Event Action: Restart timer (reset PER register) + * - Event Source Select: trigger on CODEC_TIMER_MODSTART_EVSEL = TC_EVSEL_CH0_gc = Event Channel 0 + * From 14.12.4 [8331F–AVR–04/2013] + */ + CODEC_TIMER_SAMPLING.CTRLD = TC_EVACT_RESTART_gc | CODEC_TIMER_MODSTART_EVSEL; + /* Resets the sampling counter (TCD0) to 0 - From 14.12.12 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.CNT = 0; + /* Set the period for TCD0 to ISO15693_SAMPLE_PERIOD - 1 - From 14.12.14 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.PER = ISO15693_READER_SAMPLE_PERIOD - 1; + /* Set Compare Channel C (CCC) period to half bit period - 1. (- 14 to compensate ISR timing overhead) - From 14.12.16 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.CCC = ISO15693_READER_SAMPLE_PERIOD / 2 - 14 - 1; + /** + * Temporarily disable Compare Channel C (CCC) interrupts. They'll be enabled later + * in isr_SNIFF_ISO15693_CODEC_DEMOD_READER_IN_INT0_VECT once we sensed the reader + * is sending data and we're in sync with the pulses. + * From 14.12.7 [8331F–AVR–04/2013] + */ + CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCCINTLVL_OFF_gc; + /* Clear Compare Channel C (CCC) interrupt Flags - From 14.12.10 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCCIF_bm; + + + /* Start looking out for modulation pause via interrupt. */ + /* Clear PORTB interrupt flags - From 13.13.13 [8331F–AVR–04/2013] */ + CODEC_DEMOD_IN_PORT.INTFLAGS = PORT_INT0IF_bm; + /* Use PIN1 as a source for modulation pauses. Will trigger PORTB Interrput 0 - From 13.13.11 [8331F–AVR–04/2013] */ + CODEC_DEMOD_IN_PORT.INT0MASK = CODEC_DEMOD_IN_MASK0; +} + + +/* This function implements CODEC_DEMOD_IN_INT0_VECT interrupt vector. + * It is called when a pulse is detected in CODEC_DEMOD_IN_PORT (PORTB). + * The relevatn interrupt vector is registered to CODEC_DEMOD_IN_MASK0 (PIN1) via: + * CODEC_DEMOD_IN_PORT.INT0MASK = CODEC_DEMOD_IN_MASK0; + * and unregistered writing the INT0MASK to 0 + */ +ISR_SHARED isr_SNIFF_ISO15693_CODEC_DEMOD_READER_IN_INT0_VECT(void) { + /* Start sample timer CODEC_TIMER_SAMPLING (TCD0). + * Set Counter Channel C (CCC) with relevant bitmask (TC0_CCCIF_bm), + * the period for clock sampling is specified in ReaderSniffInit. + */ + CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCCIF_bm; + /* Sets register INTCTRLB to TC_CCCINTLVL_HI_gc = (0x03<<4) to enable compare/capture for high level interrupts on Channel C (CCC) */ + CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCCINTLVL_HI_gc; + + /* Disable this interrupt as we've already sensed the relevant pulse and will use our internal clock from now on */ + CODEC_DEMOD_IN_PORT.INT0MASK = 0; +} + +/* This function is registered to CODEC_TIMER_SAMPLING (TCD0)'s Counter Channel C (CCC). + * When the timer is enabled, this is called on counter's overflow + * + * It demodulates bits received from the reader and saves them in CodecBuffer. + * + * It disables its own interrupt when receives an EOF (calling ISO15693_EOC) or when it receives garbage + */ +ISR_SHARED SNIFF_ISO15693_READER_CODEC_TIMER_SAMPLING_CCC_VECT(void) { + /* Shift demod data */ + SampleRegister = (SampleRegister << 1) | (!(CODEC_DEMOD_IN_PORT.IN & CODEC_DEMOD_IN_MASK) ? 0x01 : 0x00); + + if (++BitSampleCount == 8) { + BitSampleCount = 0; + switch (StateRegister) { + case DEMOD_VCD_SOC_STATE: + if (SampleRegister == VCD_SOC_1_OF_4_CODE) { + StateRegister = DEMOD_VCD_1_OUT_OF_4_STATE; + SampleDataCount = 0; + ModulationPauseCount = 0; + } else if (SampleRegister == VCD_SOC_1_OF_256_CODE) { + StateRegister = DEMOD_VCD_1_OUT_OF_256_STATE; + SampleDataCount = 0; + } else { // No SOC. Restart and try again, we probably received garbage. + Flags.ReaderDemodFinished = 1; + Flags.CardDemodFinished = 1; /* Mark Card Demod as finished as well to restart clean in CodecTagk (no bytes received -> no log) */ + /* Sets timer off for CODEC_TIMER_SAMPLING (TCD0) disabling clock source */ + CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_OFF_gc; + /* Sets register INTCTRLB to 0 to disable all compare/capture interrupts */ + CODEC_TIMER_SAMPLING.INTCTRLB = 0; + } + break; + + case DEMOD_VCD_1_OUT_OF_4_STATE: + if (SampleRegister == VCD_EOC_CODE) { + SNIFF_ISO15693_READER_EOC_VCD(); + } else { + uint8_t SampleData = ~SampleRegister; + if (SampleData == (0x01 << 6)) { + /* ^_^^^^^^ -> 00 */ + ModulationPauseCount++; + DataRegister >>= 2; + + } else if (SampleData == (0x01 << 4)) { + /* ^^^_^^^^ -> 01 */ + ModulationPauseCount++; + DataRegister >>= 2; + DataRegister |= 0b01 << 6; + + } else if (SampleData == (0x01 << 2)) { + /* ^^^^^_^^ -> 10 */ + ModulationPauseCount++; + DataRegister >>= 2; + DataRegister |= 0b10 << 6; + + } else if (SampleData == (0x01 << 0)) { + /* ^^^^^^^_ -> 11 */ + ModulationPauseCount++; + DataRegister >>= 2; + DataRegister |= 0b11 << 6; + } + + if (ModulationPauseCount == 4) { + ModulationPauseCount = 0; + *CodecBufferPtr = DataRegister; + ++CodecBufferPtr; + ++ByteCount; + } + } + break; + + case DEMOD_VCD_1_OUT_OF_256_STATE: + if (SampleRegister == VCD_EOC_CODE) { + SNIFF_ISO15693_READER_EOC_VCD(); + } else { + uint8_t Position = ((SampleDataCount / 2) % 256) - 1; + uint8_t SampleData = ~SampleRegister; + + if (SampleData == (0x01 << 6)) { + /* ^_^^^^^^ -> N-3 */ + DataRegister = Position - 3; + ModulationPauseCount++; + + } else if (SampleData == (0x01 << 4)) { + /* ^^^_^^^^ -> N-2 */ + DataRegister = Position - 2; + ModulationPauseCount++; + + } else if (SampleData == (0x01 << 2)) { + /* ^^^^^_^^ -> N-1 */ + DataRegister = Position - 1; + ModulationPauseCount++; + + } else if (SampleData == (0x01 << 0)) { + /* ^^^^^^^_ -> N-0 */ + DataRegister = Position - 0; + ModulationPauseCount++; + } + + if (ModulationPauseCount == 1) { + ModulationPauseCount = 0; + *CodecBufferPtr = DataRegister; + ++CodecBufferPtr; + ++ByteCount; + } + } + break; + } + SampleRegister = 0; + } + SampleDataCount++; +} + +/* This function is called from isr_ISO15693_CODEC_TIMER_SAMPLING_CCC_VECT + * when we have 8 bits in SampleRegister and they represent an end of frame. + */ +INLINE void SNIFF_ISO15693_READER_EOC_VCD(void) { + /* Mark reader data as received */ + Flags.ReaderDemodFinished = 1; + ReaderByteCount = ByteCount; /* Copy to direction-specific variable */ + + /* Sets timer off for TCD0, disabling clock source. We're done receiving data from reader and don't need to probe the antenna anymore - From 14.12.1 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_OFF_gc; + /* Disable event action for CODEC_TIMER_SAMPLING (TCD0) - From 14.12.4 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.CTRLD = TC_EVACT_OFF_gc; + /* Disable compare/capture interrupts on Channel C - From 14.12.7 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCCINTLVL_OFF_gc; + /* Clear Compare Channel C (CCC) interrupt Flags - From 14.12.10 [8331F–AVR–04/2013] */ + CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCCIF_bm; + + /* And initialize VICC->VCD sniffer */ + CardSniffInit(); +} + +///////////////////////////////////////////////// +// VICC->VCD +///////////////////////////////////////////////// + +/* Initialize card sniffing options +Note: Currently implemented only single subcarrier SOC detection + */ +INLINE void CardSniffInit(void) { + /* Reinit state variables */ + CodecBufferPtr = CodecBuffer2; + ByteCount = 0; + + if (CodecBuffer[0] & ISO15693_REQ_SUBCARRIER_DUAL) { + /** + * No support for dual subcarrier, 0xBAAAAAAD in log to mark unsupported behavior and stop here + */ + *(CodecBufferPtr++) = 0xBA; + *(CodecBufferPtr++) = 0xAA; + *(CodecBufferPtr++) = 0xAA; + *(CodecBufferPtr++) = 0xAD; + ByteCount += 4; + CardByteCount = ByteCount; /* Copy to direction-specific variable */ + + Flags.CardDemodFinished = 1; + return; + } + + /** + * Prepare ADC for antenna field sampling + * Use conversion channel 1 (0 is already used for reader field RSSI sensing), attached to + * CPU pin 42 (DEMOD-READER/2.3C). + * Use conversion channel 2, attached to CPU pin 3 (DEMOD/2.3C). + * + * Values from channel 1 (DEMOD-READER/2.3C) will be used to determine pulses levels. This channel is used + * because the signal shape from DEMOD-READER is easier to analyze with our limited resources and will + * more likely yield useful values. + * Values from channel 2 (DEMOD/2.3C) will be used to identify the first pulse threshold: ADC channel 2 + * is sampling on the same channel where analog comparator is comparing values, thus the value from + * channel 2 is the only useful threshold to identify the first pulse. + */ + ADCA.PRESCALER = ADC_PRESCALER_DIV4_gc; /* Increase ADC clock speed from default setting in AntennaLevel.h */ + ADCA.CTRLB |= ADC_FREERUN_bm; /* Set ADC as free running */ + ADCA.EVCTRL = ADC_SWEEP_012_gc; /* Enable free running sweep on channel 0, 1 and 2 */ + ADCA.CH1.MUXCTRL = ADC_CH_MUXPOS_PIN2_gc; /* Sample PORTA Pin 2 (DEMOD-READER/2.3C) in channel 1 (same pin the analog comparator is comparing to) */ + ADCA.CH1.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc; /* Single-ended input, no gain */ + ADCA.CH2.MUXCTRL = ADC_CH_MUXPOS_PIN7_gc; /* Sample PORTA Pin 7 (DEMOD/2.3C) in channel 2 */ + ADCA.CH2.CTRL = ADC_CH_INPUTMODE_SINGLEENDED_gc; /* Single-ended input, no gain */ + + /** + * CODEC_READER_TIMER will now be used to timeout VICC sniffing if no data is received before period overflow. + * The overflow interrupt has to be manually deactivated once meaningful data is received, no events will clear it. + * + * PER = maximum card wait time (1500 us). + */ + CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_DIV256_gc; /* Clocked at 1/256 CPU clock */ + CODEC_TIMER_SAMPLING.PER = 160; /* ~1500 us card response timeout */ + CODEC_TIMER_SAMPLING.INTCTRLA = TC_OVFINTLVL_HI_gc; /* Enable overflow (SOF timeout) interrupt */ + CODEC_TIMER_SAMPLING.CTRLFSET = TC_CMD_RESTART_gc; /* Reset timer once it has been configured */ + + /** + * CODEC_TIMER_TIMESTAMPS (TCD1) will be used to count the peaks identified by ACA while sniffing VICC data + * + * CCA = 3 pulses, to update the threshold to a more suitable value for upcoming pulses + */ + CODEC_TIMER_TIMESTAMPS.CTRLA = TC_CLKSEL_EVCH2_gc; /* Using Event channel 2 as an input */ + CODEC_TIMER_TIMESTAMPS.CCA = 3; + CODEC_TIMER_TIMESTAMPS.INTCTRLB = TC_CCAINTLVL_HI_gc; /* Enable CCA interrupt */ + CODEC_TIMER_TIMESTAMPS.CTRLFSET = TC_CMD_RESTART_gc; /* Reset counter once it has been configured */ + + /** + * CODEC_TIMER_LOADMOD (TCE0) has multiple usages: + * - Period overflow samples second half-bit (called 37,76 us after bit start) - while decoding data + * - Compare channel A: + * - filters erroneous VICC->VCD SOC identification + * - prepares the codec for data demodulation when a SOF is found + * - Compare channel C samples the first half-bit (called 18,88 us after bit start) - while decoding data + * + * It is restarted on every event on event channel 2 until the first 24 pulses of the SOC have been received, then + * it restarts on period overflow (after one full logic bit). + * + * Interrupt on compare channel A will be called every 64 clock cycles, unless this timer is reset by a new + * pulse. This means that interrupt on CCA will be called after some pulses, be it one or more. + * When CCA is called and we have received too few pulses, it was just noise on the field, so we have to start + * searching again for a SOF. On the other hand, if we received multiple pulses, we can finally start demodulation. + * Also, compare channel A can then be disabled. + * + * Interrupt on compare channel C will be called after the first half-bit and will save it in the samples register. + * + * Overflow interrupt will be called after the second half-bit and will save it in the samples register. + * Every 16 half-bits, this will also convert the samples to a byte and store it in the codec buffer. + * It will also check for EOF to stop card decoding. + * + * PER = one logic bit duration (27,76 us) + * CCA = duration of a single pulse (2,36 us: half hi + half low) + * CCC = half-bit duration (18,88 us) + */ + CODEC_TIMER_LOADMOD.CTRLA = TC_CLKSEL_DIV1_gc; /* Clocked at 27.12 MHz */ + CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_RESTART_gc | TC_EVSEL_CH2_gc; /* Restart this timer on every event on channel 2 (pulse detected by AC) */ + CODEC_TIMER_LOADMOD.PER = 1024 - 8; /* One full logic bit duration, slightly shorter on first run to make room for SOC detection algoritm */ + CODEC_TIMER_LOADMOD.PERBUF = 1024 - 1; /* One full logic bit duration - 1 (accommodate for small clock drift) */ + CODEC_TIMER_LOADMOD.CCA = 64; /* Single pulse width */ + CODEC_TIMER_LOADMOD.CCC = 512 - 8; /* Same as PER, use a slightly shorter CCC period on first run */ + CODEC_TIMER_LOADMOD.CCCBUF = 512; /* After first CCC, use actual CCC period (will be written when UPDATE condition is met, thus after first period hit) */ + CODEC_TIMER_LOADMOD.INTCTRLA = TC_OVFINTLVL_OFF_gc; /* Keep overflow interrupt (second half-bit) disabled */ + CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCAINTLVL_OFF_gc | TC_CCBINTLVL_OFF_gc | TC_CCCINTLVL_OFF_gc; /* Keep all compare interrupt disabled, they will be enabled later on */ + CODEC_TIMER_LOADMOD.CTRLFSET = TC_CMD_RESTART_gc; /* Reset timer */ + + /** + * Get current signal amplitude and record it as "silence" + * + * This can't be moved closer to ADC configuration since the ADC has to be populated with valid + * values and it takes 7*4 clock cycles to do so (7 ADC stages * 4 ADC clock prescaler) + */ + uint32_t accumulator; + accumulator = ADCA.CH2RES - ANTENNA_LEVEL_OFFSET; /* PORTA Pin 7 (DEMOD/2.3C) - CPU pin 7 */ + for (uint8_t i = 0; i < 127; i++) { /* Add 127 more values */ + accumulator += ADCA.CH2RES - ANTENNA_LEVEL_OFFSET; + } + DemodFloorNoiseLevel = (uint16_t)(accumulator >> 7); /* Divide by 2^7=128 */ + /** + * Typical values with my CR95HF reader: + * ReaderFloorNoiseLevel: ~900 (measured on ADCA.CH1RES) + * DemodFloorNoiseLevel: ~1500 + */ + + /* Reconfigure ADC to sample only the first 2 channels from now on (no need for CH2 once the noise level has been found) */ + ADCA.EVCTRL = ADC_SWEEP_01_gc; + + /* Write threshold to the DAC channel 0 (connected to Analog Comparator positive input) */ + DACB.CH0DATA = DemodFloorNoiseLevel + (DemodFloorNoiseLevel >> 3); /* Slightly increase DAC output to ease triggering */ + DACB.CH0DATA |= -( (DACB.CH0DATA & 0xFFF) < DemodFloorNoiseLevel); /* Branchfree saturating addition */ + + /** + * Finally, now that we have the DAC set up, configure analog comparator A (the only one in this MCU) + * channel 0 (the only one that can be connecte to the DAC) to recognize carrier pulses modulated by + * the VICC against the correct threshold coming from the DAC. + */ + ACA.AC0MUXCTRL = AC_MUXPOS_DAC_gc | AC_MUXNEG_PIN7_gc; /* Tigger when DAC signal is above PORTA Pin 7 (DEMOD/2.3C) */ + /* enable AC | high speed mode | large hysteresis | sample on rising edge | high level interrupts */ + /* Hysteresis is not actually needed, but appeared to be working and sounds like it might be more robust */ + ACA.AC0CTRL = AC_ENABLE_bm | AC_HSMODE_bm | AC_HYSMODE_LARGE_gc | AC_INTMODE_RISING_gc | AC_INTLVL_HI_gc; + + /* This function ends ~100 us after last VCD pulse */ +} + +/** + * This function restores all the configured peripherals (ADC/AC/timers) to a clean state + * + * Not inlined, since once we need to call this, there won't be any strict timing constraint + */ +void CardSniffDeinit(void) { + /* Restore ADC settings as per AntennaLevel.h */ + ADCA.PRESCALER = ADC_PRESCALER_DIV32_gc; /* Reduce clock */ + ADCA.CTRLB = ADC_RESOLUTION_12BIT_gc; /* Stop free running ADC */ + ADCA.EVCTRL = 0; /* Stop channel sweep or any ADC event */ + /* Ignore channel 1 and 2 mux settings (no "off" state) */ + + /* Disable all timers interrupts */ + CODEC_TIMER_SAMPLING.INTCTRLA = TC_OVFINTLVL_OFF_gc; + CODEC_TIMER_TIMESTAMPS.INTCTRLB = TC_CCAINTLVL_OFF_gc; + CODEC_TIMER_LOADMOD.INTCTRLA = TC_OVFINTLVL_OFF_gc; + CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCAINTLVL_OFF_gc | TC_CCBINTLVL_OFF_gc | TC_CCCINTLVL_OFF_gc; + + /* Stop timer/counters disabling clock sources */ + CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_OFF_gc; + CODEC_TIMER_TIMESTAMPS.CTRLA = TC_CLKSEL_OFF_gc; + CODEC_TIMER_LOADMOD.CTRLA = TC_CLKSEL_OFF_gc; + + /* Reset ACA AC0 to default setting */ + ACA.AC0MUXCTRL = AC_MUXPOS_DAC_gc | AC_MUXNEG_PIN7_gc; /* This actually was unchanged */ + ACA.AC0CTRL = CODEC_AC_DEMOD_SETTINGS; /* Disable interrupt as well */ +} + + +/** + * This interrupt is called on every rising edge sensed by the analog comparator + * and it enables the spurious pulse filtering interrupt (CODEC_TIMER_LOADMOD CCA). + * If we did not receive a SOC but, indeed, noise, this interrupt will be enabled again. + */ +ISR_SHARED isr_SNIFF_ISO15693_ACA_AC0_VECT(void) { + CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCAINTLVL_HI_gc; /* Enable level 0 CCA interrupt to filter spurious pulses and find SOC */ + + ACA.AC0CTRL = AC_ENABLE_bm | AC_HSMODE_bm | AC_HYSMODE_LARGE_gc | AC_INTMODE_RISING_gc | AC_INTLVL_OFF_gc; /* Disable this interrupt */ + + DACB.CH0DATA = DemodFloorNoiseLevel + (DemodFloorNoiseLevel >> 2); /* Blindly increase threshold after 1 pulse */ + DACB.CH0DATA |= -( (DACB.CH0DATA & 0xFFF) < DemodFloorNoiseLevel); /* Branchfree saturating addition */ + /* Note: by the time the DAC has changed its output, we're already after the 2nd pulse */ +} + +/** + * This interrupt is called after 3 subcarrier pulses and increases the threshold + */ +ISR_SHARED isr_SNIFF_ISO15693_CODEC_TIMER_TIMESTAMPS_CCA_VECT(void) { + + uint16_t TempRead = ADCA.CH1RES; /* Can't possibly be greater than 0xFFF since the dac has 12 bit res */ + DACB.CH0DATA = TempRead - ANTENNA_LEVEL_OFFSET; /* Further increase DAC output after 3 pulses with value from PORTA Pin 2 (DEMOD-READER/2.3) */ + DACB.CH0DATA &= -(DACB.CH0DATA <= TempRead); /* Branchfree saturating subtraction */ + + CODEC_TIMER_TIMESTAMPS.INTCTRLB = TC_CCAINTLVL_OFF_gc; /* Disable this interrupt */ +} + +/** + * This interrupt is called every 64 clock cycles (= once per pulse) unless the timer is reset. + * This means it will be invoked only if, after a pulse, we don't receive another one. + * + * This will either find a noise or the end of the SOC. If we've received noise, most likely, + * we received few pulses. The soc is made of 24 pulses, so if VICC modulation actually started, + * we should have a relevant number of pulses + */ +ISR_SHARED isr_SNIFF_ISO15693_CODEC_TIMER_LOADMOD_CCA_VECT(void) { + if (CODEC_TIMER_TIMESTAMPS.CNT < 15) { + /* We most likely received garbage */ + + CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCAINTLVL_OFF_gc; /* Disable all compare interrupts, including this one */ + + DACB.CH0DATA = DemodFloorNoiseLevel + (DemodFloorNoiseLevel >> 3); /* Restore DAC output (AC negative comparation threshold) to pre-AC0 interrupt update value */ + DACB.CH0DATA |= -( (DACB.CH0DATA & 0xFFF) < DemodFloorNoiseLevel); /* Branchfree saturating addition */ + ACA.AC0CTRL |= AC_INTLVL_HI_gc; /* Re-enable analog comparator interrupt to search for another pulse */ + + CODEC_TIMER_TIMESTAMPS.INTCTRLB = TC_CCAINTLVL_HI_gc; /* Re enable CCA interrupt in case it was triggered and then is now disabled */ + } else { + /** + * Received a lot of pulses, we can assume it was the SOF, prepare interrupts for data demodulation. + * Data demodulation interrupt will take care of checking if we receive the two remaining half-bits + * of the sof and will then convalidate that these pulses were actually part of the SOF + */ + + CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCCINTLVL_HI_gc; /* Enable CCC (first half-bit decoding), disable CCA (spurious pulse timeout) */ + CODEC_TIMER_LOADMOD.INTCTRLA = TC_OVFINTLVL_HI_gc; /* Enable overflow (second half-bit decoding) interrupt */ + CODEC_TIMER_LOADMOD.CTRLD = TC_EVACT_OFF_gc; /* Disable timer resetting on every AC0 event */ + + /** + * Prepare registers for data demodulation + * This is a bit like cheating. We assumed this is a real SOC, so we can pretend we've already received + * the first 3 unmodulated and 3 modulated periods (binary: 0b000111) which compose the SOC. + * The two upcoming half-bits (0b01) will be shifted in during data decoding. + */ + SampleRegister = 0b000111; /* = 0x7 */ + BitSampleCount = 4 + 3; /* Pretend we've already received 8 empty half-bits and the above 6 half-bits */ + StateRegister = DEMOD_VICC_SOC_STATE; + } + + CODEC_TIMER_TIMESTAMPS.CNT = 0; /* Clear pulses counter nonetheless */ +} + +/** + * This interrupt is called on the first half-bit + */ +ISR(CODEC_TIMER_LOADMOD_CCC_VECT) { + /** + * This interrupt is called on every odd half-bit, thus we don't need to do any check, + * just append to the sample register. + */ + SampleRegister = (SampleRegister << 1) | (CODEC_TIMER_TIMESTAMPS.CNT > 4); /* Using 4 as a discriminating factor to allow for slight errors in pulses counting. */ + /* Don't increase BitSampleCount, since this is only the first half of a bit */ + + CODEC_TIMER_TIMESTAMPS.CNT = 0; /* Clear count register for next half-bit */ +} + +/** + * This interrupt handles the VICC SOF timeout when the card does not answer + * and restarts reader sniffing + */ +ISR(CODEC_TIMER_SAMPLING_OVF_VECT) { + Flags.CardDemodFinished = 1; + + /* Call cleanup function */ + CardSniffDeinit(); +} + +/** + * This interrupt is called on the second bit-half to decode it. + * It replaces isr_SNIFF_ISO15693_CODEC_TIMER_LOADMOD_OVF_VECT_timeout once the SOF is received + */ +ISR_SHARED isr_SNIFF_ISO15693_CODEC_TIMER_LOADMOD_OVF_VECT(void) { + /** + * This interrupt is called on every even half-bit, we then need to check the content of the register + */ + SampleRegister = (SampleRegister << 1) | (CODEC_TIMER_TIMESTAMPS.CNT > 4); /* Using 4 as a discriminating factor to allow for slight errors in pulses counting. */ + BitSampleCount++; + CODEC_TIMER_TIMESTAMPS.CNT = 0; /* Clear count register for next half-bit */ + + // char tmpBuf[10]; + // snprintf(tmpBuf, 10, "BSC: %d\n", BitSampleCount); + // TerminalSendString(tmpBuf); + + if (BitSampleCount == 8) { /* We have 16 half-bits in SampleRegister at this point */ + BitSampleCount = 0; + + switch (StateRegister) { + case DEMOD_VICC_SOC_STATE: + if (SampleRegister == VICC_SOC_CODE) { + /* We've actually received a SOF after the previous (mostly unknown) train of pulses */ + CODEC_TIMER_SAMPLING.INTCTRLA = TC_OVFINTLVL_OFF_gc; /* Disable VICC SOF timeout handler */ + StateRegister = DEMOD_VICC_DATA; + } else { + /* No SOC. The train of pulses was actually garbage. */ + + /* Restore DAC output to intial value */ + DACB.CH0DATA = DemodFloorNoiseLevel + (DemodFloorNoiseLevel >> 3); + DACB.CH0DATA |= -( (DACB.CH0DATA & 0xFFF) < DemodFloorNoiseLevel); /* Branchfree saturating addition */ + + /* Re enable AC interrupt */ + ACA.AC0CTRL = AC_ENABLE_bm | AC_HSMODE_bm | AC_HYSMODE_LARGE_gc | AC_INTMODE_RISING_gc | AC_INTLVL_HI_gc; + + /* Restore other interrupts as per original setup (same as CardSniffInit) */ + CODEC_TIMER_TIMESTAMPS.INTCTRLB = TC_CCAINTLVL_HI_gc; /* Re enable 3rd pulse thresh update */ + CODEC_TIMER_LOADMOD.INTCTRLA = TC_OVFINTLVL_OFF_gc; /* Disable overflow interrupt (second half-bit) */ + CODEC_TIMER_LOADMOD.INTCTRLB = TC_CCAINTLVL_OFF_gc | TC_CCBINTLVL_OFF_gc | TC_CCCINTLVL_OFF_gc; /* Disable all compare channel interrupts */ + + /* Reset timers */ + CODEC_TIMER_TIMESTAMPS.CTRLFSET = TC_CMD_RESTART_gc; /* Restart peak counter */ + CODEC_TIMER_LOADMOD.CTRLFSET = TC_CMD_RESTART_gc; /* Reset pulse train timer */ + + /* Reset periods which were possibly modified */ + CODEC_TIMER_LOADMOD.PER = 1024 - 8; /* One full logic bit duration, slightly shorter on first run to make room for SOC detection algoritm */ + CODEC_TIMER_LOADMOD.PERBUF = 1024 - 1; /* One full logic bit duration - 1 (accommodate for small clock drift) */ + CODEC_TIMER_LOADMOD.CCC = 512 - 8; /* Same as PER, use a slightly shorter CCC period on first run */ + CODEC_TIMER_LOADMOD.CCCBUF = 512; /* After first CCC, use actual CCC period (will be written when UPDATE condition is met, thus after first period hit) */ + + /* No need to (dis|en)able CODEC_TIMER_SAMPLING OVF interrupt (we're still waiting for a valid SOF and that interrupt wasn't disabled) */ + } + break; + + case DEMOD_VICC_DATA: + if (SampleRegisterL == VICC_EOC_CODE) { + Flags.CardDemodFinished = 1; + + CardByteCount = ByteCount; /* Copy to direction-specific variable */ + + /* Call cleanup function */ + CardSniffDeinit(); + } else if ( + (SampleRegisterL & 0b00000011) == 0b00000011 || /* Ugly, I know */ + (SampleRegisterL & 0b00000011) == 0b00000000 || + (SampleRegisterL & 0b00001100) == 0b00001100 || + (SampleRegisterL & 0b00001100) == 0b00000000 || + (SampleRegisterL & 0b00110000) == 0b00110000 || + (SampleRegisterL & 0b00110000) == 0b00000000 || + (SampleRegisterL & 0b11000000) == 0b11000000 || + (SampleRegisterL & 0b11000000) == 0b00000000 || + (SampleRegisterH & 0b00000011) == 0b00000011 || + (SampleRegisterH & 0b00000011) == 0b00000000 || + (SampleRegisterH & 0b00001100) == 0b00001100 || + (SampleRegisterH & 0b00001100) == 0b00000000 || + (SampleRegisterH & 0b00110000) == 0b00110000 || + (SampleRegisterH & 0b00110000) == 0b00000000 || + (SampleRegisterH & 0b11000000) == 0b11000000 || + (SampleRegisterH & 0b11000000) == 0b00000000 + ) { + /** + * Check for invalid data coding (11 or 00 bit-halfs). + * Write 0x0BADDA7A in log to mark broken frame and stop here: we have + * no knowledge about where the EOF could be + */ + + *(CodecBufferPtr++) = 0x0B; + *(CodecBufferPtr++) = 0xAD; + *(CodecBufferPtr++) = 0xDA; + *(CodecBufferPtr++) = 0x7A; + ByteCount += 4; + + Flags.CardDemodFinished = 1; + + CardByteCount = ByteCount; /* Copy to direction-specific variable */ + + /* Call cleanup function */ + CardSniffDeinit(); + + } else { + DataRegister = ( (SampleRegisterL & 0b00000011) == 0b00000001) << 3; + DataRegister |= ( (SampleRegisterL & 0b00001100) == 0b00000100) << 2; + DataRegister |= ( (SampleRegisterL & 0b00110000) == 0b00010000) << 1; + DataRegister |= ( (SampleRegisterL & 0b11000000) == 0b01000000); /* Bottom 2 bits */ + DataRegister |= ( (SampleRegisterH & 0b00000011) == 0b00000001) << 7; + DataRegister |= ( (SampleRegisterH & 0b00001100) == 0b00000100) << 6; + DataRegister |= ( (SampleRegisterH & 0b00110000) == 0b00010000) << 5; + DataRegister |= ( (SampleRegisterH & 0b11000000) == 0b01000000) << 4; + + *CodecBufferPtr = DataRegister; + CodecBufferPtr++; + ByteCount++; + if (ByteCount == 256) { + /* Somehow missed the EOF and running out of buffer */ + Flags.CardDemodFinished = 1; + + /* Call cleanup function */ + CardSniffDeinit(); + }; + } + break; + } + } + +} + + +///////////////////////////////////////////////// +// External interface +///////////////////////////////////////////////// + +void SniffISO15693CodecInit(void) { + CodecInitCommon(); + + /** + * Register function handlers to shared ISR + */ + /* CODEC_TIMER_SAMPLING (TCD0)'s Counter Channel C (CCC) - Reader demod */ + isr_func_TCD0_CCC_vect = &SNIFF_ISO15693_READER_CODEC_TIMER_SAMPLING_CCC_VECT; + /* CODEC_DEMOD_IN_PORT (PORTB) interrupt 0 - Reader demod */ + isr_func_CODEC_DEMOD_IN_INT0_VECT = &isr_SNIFF_ISO15693_CODEC_DEMOD_READER_IN_INT0_VECT; + /* CODEC_TIMER_TIMESTAMPS (TCD1)'s Counter Channel C (CCC) - Card demod */ + isr_func_CODEC_TIMER_TIMESTAMPS_CCA_VECT = &isr_SNIFF_ISO15693_CODEC_TIMER_TIMESTAMPS_CCA_VECT; /* Updated threshold after 3 pulses */ + /* CODEC_TIMER_LOADMOD (TCE0)'s Counter Channel A (CCA) and overflow (OVF) - Card demod */ + isr_func_CODEC_TIMER_LOADMOD_CCA_VECT = &isr_SNIFF_ISO15693_CODEC_TIMER_LOADMOD_CCA_VECT; /* Spurious pulses and SOC detection */ + /* CCC (first half-bit decoder) ISR is not shared, thus doesn't need to be assigned here */ + isr_func_CODEC_TIMER_LOADMOD_OVF_VECT = &isr_SNIFF_ISO15693_CODEC_TIMER_LOADMOD_OVF_VECT; /* Second half-bit decoder */ + /* Analog comparator A (ACA) channel 0 interrupt handler - Card demod */ + isr_func_ACA_AC0_vect = &isr_SNIFF_ISO15693_ACA_AC0_VECT; /* Detect first card pulse and enable demod interrupts */ + + /** + * Route events from the analog comparator (which will be configured later) on the event system + * using channel 2 (channel 0 and 1 are used by the antenna). + * These events will be counted by CODEC_TIMER_TIMESTAMPS and will (sometimes) + * reset CODEC_TIMER_LOADMOD. + */ + EVSYS.CH2MUX = EVSYS_CHMUX_ACA_CH0_gc; /* Route analog comparator channel 0 events on event system channel 2 */ + + /* Change DAC reference source to Internal 1V (same as ADC source) */ + DACB.CTRLC = DAC_REFSEL_INT1V_gc; + + ReaderSniffInit(); +} + +void SniffISO15693CodecDeInit(void) { + /* Gracefully shutdown codec */ + CODEC_DEMOD_IN_PORT.INT0MASK = 0; + + /* Reset global variables to default values */ + CodecBufferPtr = CodecBuffer; + Flags.ReaderDemodFinished = 0; + Flags.CardDemodFinished = 0; + StateRegister = DEMOD_VCD_SOC_STATE; + DataRegister = 0; + SampleRegister = 0; + BitSampleCount = 0; + SampleDataCount = 0; + ModulationPauseCount = 0; + ByteCount = 0; + ShiftRegister = 0; + + /* Disable sample timer */ + /* Sets timer off for CODEC_TIMER_SAMPLING (TCD0) disabling clock source */ + CODEC_TIMER_SAMPLING.CTRLA = TC_CLKSEL_OFF_gc; + /* Disable event action for CODEC_TIMER_SAMPLING (TCD0) */ + CODEC_TIMER_SAMPLING.CTRLD = TC_EVACT_OFF_gc; + /* Sets register INTCTRLB to TC_CCCINTLVL_OFF_gc = (0x00<<4) to disable compare/capture C interrupts */ + CODEC_TIMER_SAMPLING.INTCTRLB = TC_CCCINTLVL_OFF_gc; + /* Restore Counter Channel C (CCC) interrupt mask (TC0_CCCIF_bm) */ + CODEC_TIMER_SAMPLING.INTFLAGS = TC0_CCCIF_bm; + + CodecSetDemodPower(false); + + /* Stop AC0 events routing on channel 2 */ + EVSYS.CH2MUX = EVSYS_CHMUX_OFF_gc; + + /* Restore default DAC reference to Codec.h setting */ + DACB.CTRLC = DAC_REFSEL_AVCC_gc; +} + +void SniffISO15693CodecTask(void) { + if (Flags.ReaderDemodFinished) { + Flags.ReaderDemodFinished = 0; + + if (ReaderByteCount > 0) { + LogEntry(LOG_INFO_CODEC_SNI_READER_DATA, CodecBuffer, ReaderByteCount); + LEDHook(LED_CODEC_RX, LED_PULSE); + + SniffTrafficSource = TRAFFIC_READER; + ApplicationProcess(CodecBuffer, ReaderByteCount); + } + + } + + if(Flags.CardDemodFinished) { + Flags.CardDemodFinished = 0; + + if (CardByteCount > 0) { + LogEntry(LOG_INFO_CODEC_SNI_CARD_DATA, CodecBuffer2, CardByteCount); + LEDHook(LED_CODEC_RX, LED_PULSE); + + SniffTrafficSource = TRAFFIC_CARD; + ApplicationProcess(CodecBuffer2, CardByteCount); + /* Note: the application might want to know if the frame is broken, need to extern bBrokenFrame */ + } + + ReaderSniffInit(); + } +} diff --git a/Firmware/Chameleon-Mini/Codec/SniffISO15693.h b/Firmware/Chameleon-Mini/Codec/SniffISO15693.h new file mode 100644 index 0000000..ef7d1d2 --- /dev/null +++ b/Firmware/Chameleon-Mini/Codec/SniffISO15693.h @@ -0,0 +1,41 @@ +/* + * SniffISO15693.h + * + * Created on: 05.11.2019 + * Author: ceres-c + */ + +#ifndef SNIFF_ISO15693_H_ +#define SNIFF_ISO15693_H_ + +#include "Terminal/CommandLine.h" + +#define ISO15693_APP_NO_RESPONSE 0x0000 + +#define SUBCARRIER_1 32 +#define SUBCARRIER_2 28 +#define SUBCARRIER_OFF 0 +#define SOF_PATTERN 0x1D +#define EOF_PATTERN 0xB8 + +//Used when checking the request sent from the reader +#define ISO15693_REQ_SUBCARRIER_SINGLE 0x00 +#define ISO15693_REQ_SUBCARRIER_DUAL 0x01 +#define ISO15693_REQ_DATARATE_LOW 0x00 +#define ISO15693_REQ_DATARATE_HIGH 0x02 + +/* Codec Interface */ +void SniffISO15693CodecInit(void); +void SniffISO15693CodecDeInit(void); +void SniffISO15693CodecTask(void); + +/* Application Interface */ +void SniffISO15693CodecStart(void); +void SniffISO15693CodecReset(void); + +/* Internal functions */ +INLINE void SNIFF_ISO15693_READER_EOC_VCD(void); +INLINE void CardSniffInit(void); + + +#endif /* SNIFF_ISO15693_H_ */ diff --git a/Firmware/Chameleon-Mini/Common.h b/Firmware/Chameleon-Mini/Common.h index 658082e..20c6603 100644 --- a/Firmware/Chameleon-Mini/Common.h +++ b/Firmware/Chameleon-Mini/Common.h @@ -17,6 +17,9 @@ #define ODD_PARITY(Value) OddParityBit(Value)//(parity_even_bit(Value) ? 0 : 1) +#define ISR_SHARED \ + void __attribute__((signal)) // This function type has to be used for all the interrupt handlers that have to be changed at runtime + #define INLINE \ static inline __attribute__((always_inline)) diff --git a/Firmware/Chameleon-Mini/Configuration.c b/Firmware/Chameleon-Mini/Configuration.c index c26ff6e..e8a3ff8 100644 --- a/Firmware/Chameleon-Mini/Configuration.c +++ b/Firmware/Chameleon-Mini/Configuration.c @@ -51,6 +51,9 @@ static const MapEntryType PROGMEM ConfigurationMap[] = { #ifdef CONFIG_ISO14443A_READER_SUPPORT { .Id = CONFIG_ISO14443A_READER, .Text = "ISO14443A_READER" }, #endif +#ifdef CONFIG_NTAG215_SUPPORT + { .Id = CONFIG_NTAG215, .Text = "NTAG215" }, +#endif #ifdef CONFIG_VICINITY_SUPPORT { .Id = CONFIG_VICINITY, .Text = "VICINITY" }, #endif @@ -82,7 +85,10 @@ static void ApplicationTickDummy(void) {} static uint16_t ApplicationProcessDummy(uint8_t *ByteBuffer, uint16_t ByteCount) { return 0; } static void ApplicationGetUidDummy(ConfigurationUidType Uid) { } static void ApplicationSetUidDummy(ConfigurationUidType Uid) { } - +static void ApplicationGetAtqaDummy(uint16_t * Atqa) { *Atqa = CONFIGURATION_DUMMY_ATQA; } +static void ApplicationSetAtqaDummy(uint16_t Atqa) { } +static void ApplicationGetSakDummy(uint8_t * Sak) { *Sak = CONFIGURATION_DUMMY_SAK; } +static void ApplicationSetSakDummy(uint8_t Sak) { } static const PROGMEM ConfigurationType ConfigurationTable[] = { [CONFIG_NONE] = { @@ -96,6 +102,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = ApplicationProcessDummy, .ApplicationGetUidFunc = ApplicationGetUidDummy, .ApplicationSetUidFunc = ApplicationSetUidDummy, + .ApplicationGetSakFunc = ApplicationGetSakDummy, + .ApplicationSetSakFunc = ApplicationSetSakDummy, + .ApplicationGetAtqaFunc = ApplicationGetAtqaDummy, + .ApplicationSetAtqaFunc = ApplicationSetAtqaDummy, .UidSize = 0, .MemorySize = 0, .ReadOnly = true, @@ -113,6 +123,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = MifareUltralightAppProcess, .ApplicationGetUidFunc = MifareUltralightGetUid, .ApplicationSetUidFunc = MifareUltralightSetUid, + .ApplicationGetSakFunc = MifareUltralightGetSak, + .ApplicationSetSakFunc = MifareUltralightSetSak, + .ApplicationGetAtqaFunc = MifareUltralightGetAtqa, + .ApplicationSetAtqaFunc = MifareUltralightSetAtqa, .UidSize = MIFARE_ULTRALIGHT_UID_SIZE, .MemorySize = MIFARE_ULTRALIGHT_MEM_SIZE, .ReadOnly = false, @@ -129,6 +143,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = MifareUltralightAppProcess, .ApplicationGetUidFunc = MifareUltralightGetUid, .ApplicationSetUidFunc = MifareUltralightSetUid, + .ApplicationGetSakFunc = MifareUltralightGetSak, + .ApplicationSetSakFunc = MifareUltralightSetSak, + .ApplicationGetAtqaFunc = MifareUltralightGetAtqa, + .ApplicationSetAtqaFunc = MifareUltralightSetAtqa, .UidSize = MIFARE_ULTRALIGHT_UID_SIZE, .MemorySize = MIFARE_ULTRALIGHTC_MEM_SIZE, .ReadOnly = false, @@ -145,6 +163,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = MifareUltralightAppProcess, .ApplicationGetUidFunc = MifareUltralightGetUid, .ApplicationSetUidFunc = MifareUltralightSetUid, + .ApplicationGetSakFunc = MifareUltralightGetSak, + .ApplicationSetSakFunc = MifareUltralightSetSak, + .ApplicationGetAtqaFunc = MifareUltralightGetAtqa, + .ApplicationSetAtqaFunc = MifareUltralightSetAtqa, .UidSize = MIFARE_ULTRALIGHT_UID_SIZE, .MemorySize = MIFARE_ULTRALIGHT_EV11_MEM_SIZE, .ReadOnly = false, @@ -161,6 +183,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = MifareUltralightAppProcess, .ApplicationGetUidFunc = MifareUltralightGetUid, .ApplicationSetUidFunc = MifareUltralightSetUid, + .ApplicationGetSakFunc = MifareUltralightGetSak, + .ApplicationSetSakFunc = MifareUltralightSetSak, + .ApplicationGetAtqaFunc = MifareUltralightGetAtqa, + .ApplicationSetAtqaFunc = MifareUltralightSetAtqa, .UidSize = MIFARE_ULTRALIGHT_UID_SIZE, .MemorySize = MIFARE_ULTRALIGHT_EV12_MEM_SIZE, .ReadOnly = false, @@ -179,6 +205,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = MifareClassicAppProcess, .ApplicationGetUidFunc = MifareClassicGetUid, .ApplicationSetUidFunc = MifareClassicSetUid, + .ApplicationGetSakFunc = MifareClassicGetSak, + .ApplicationSetSakFunc = MifareClassicSetSak, + .ApplicationGetAtqaFunc = MifareClassicGetAtqa, + .ApplicationSetAtqaFunc = MifareClassicSetAtqa, .UidSize = MIFARE_CLASSIC_UID_SIZE, .MemorySize = MIFARE_CLASSIC_MINI_MEM_SIZE, .ReadOnly = false, @@ -197,6 +227,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = MifareClassicAppProcess, .ApplicationGetUidFunc = MifareClassicGetUid, .ApplicationSetUidFunc = MifareClassicSetUid, + .ApplicationGetSakFunc = MifareClassicGetSak, + .ApplicationSetSakFunc = MifareClassicSetSak, + .ApplicationGetAtqaFunc = MifareClassicGetAtqa, + .ApplicationSetAtqaFunc = MifareClassicSetAtqa, .UidSize = MIFARE_CLASSIC_UID_SIZE, .MemorySize = MIFARE_CLASSIC_1K_MEM_SIZE, .ReadOnly = false, @@ -215,6 +249,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = MifareClassicAppProcess, .ApplicationGetUidFunc = MifareClassicGetUid, .ApplicationSetUidFunc = MifareClassicSetUid, + .ApplicationGetSakFunc = MifareClassicGetSak, + .ApplicationSetSakFunc = MifareClassicSetSak, + .ApplicationGetAtqaFunc = MifareClassicGetAtqa, + .ApplicationSetAtqaFunc = MifareClassicSetAtqa, .UidSize = ISO14443A_UID_SIZE_DOUBLE, .MemorySize = MIFARE_CLASSIC_1K_MEM_SIZE, .ReadOnly = false, @@ -233,6 +271,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = MifareClassicAppProcess, .ApplicationGetUidFunc = MifareClassicGetUid, .ApplicationSetUidFunc = MifareClassicSetUid, + .ApplicationGetSakFunc = MifareClassicGetSak, + .ApplicationSetSakFunc = MifareClassicSetSak, + .ApplicationGetAtqaFunc = MifareClassicGetAtqa, + .ApplicationSetAtqaFunc = MifareClassicSetAtqa, .UidSize = MIFARE_CLASSIC_UID_SIZE, .MemorySize = MIFARE_CLASSIC_4K_MEM_SIZE, .ReadOnly = false, @@ -251,6 +293,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = MifareClassicAppProcess, .ApplicationGetUidFunc = MifareClassicGetUid, .ApplicationSetUidFunc = MifareClassicSetUid, + .ApplicationGetSakFunc = MifareClassicGetSak, + .ApplicationSetSakFunc = MifareClassicSetSak, + .ApplicationGetAtqaFunc = MifareClassicGetAtqa, + .ApplicationSetAtqaFunc = MifareClassicSetAtqa, .UidSize = ISO14443A_UID_SIZE_DOUBLE, .MemorySize = MIFARE_CLASSIC_4K_MEM_SIZE, .ReadOnly = false, @@ -269,6 +315,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = MifareClassicAppProcess, .ApplicationGetUidFunc = MifareClassicGetUid, .ApplicationSetUidFunc = MifareClassicSetUid, + .ApplicationGetSakFunc = MifareClassicGetSak, + .ApplicationSetSakFunc = MifareClassicSetSak, + .ApplicationGetAtqaFunc = MifareClassicGetAtqa, + .ApplicationSetAtqaFunc = MifareClassicSetAtqa, .UidSize = MIFARE_CLASSIC_UID_SIZE, .MemorySize = MIFARE_CLASSIC_1K_MEM_SIZE, .ReadOnly = false, @@ -287,6 +337,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = MifareClassicAppProcess, .ApplicationGetUidFunc = MifareClassicGetUid, .ApplicationSetUidFunc = MifareClassicSetUid, + .ApplicationGetSakFunc = MifareClassicGetSak, + .ApplicationSetSakFunc = MifareClassicSetSak, + .ApplicationGetAtqaFunc = MifareClassicGetAtqa, + .ApplicationSetAtqaFunc = MifareClassicSetAtqa, .UidSize = MIFARE_CLASSIC_UID_SIZE, .MemorySize = MIFARE_CLASSIC_4K_MEM_SIZE, .ReadOnly = false, @@ -305,6 +359,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = Sniff14443AAppProcess, .ApplicationGetUidFunc = ApplicationGetUidDummy, .ApplicationSetUidFunc = ApplicationSetUidDummy, + .ApplicationGetSakFunc = ApplicationGetSakDummy, + .ApplicationSetSakFunc = ApplicationSetSakDummy, + .ApplicationGetAtqaFunc = ApplicationGetAtqaDummy, + .ApplicationSetAtqaFunc = ApplicationSetAtqaDummy, .UidSize = 0, .MemorySize = 0, .ReadOnly = true, @@ -323,6 +381,10 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .ApplicationProcessFunc = Reader14443AAppProcess, .ApplicationGetUidFunc = ApplicationGetUidDummy, .ApplicationSetUidFunc = ApplicationSetUidDummy, + .ApplicationGetSakFunc = ApplicationGetSakDummy, + .ApplicationSetSakFunc = ApplicationSetSakDummy, + .ApplicationGetAtqaFunc = ApplicationGetAtqaDummy, + .ApplicationSetAtqaFunc = ApplicationSetAtqaDummy, .UidSize = 0, .MemorySize = 0, .ReadOnly = false, @@ -349,14 +411,14 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { #endif #ifdef CONFIG_ISO15693_SNIFF_SUPPORT [CONFIG_ISO15693_SNIFF] = { - .CodecInitFunc = ISO15693CodecInit, - .CodecDeInitFunc = ISO15693CodecDeInit, - .CodecTaskFunc = ISO15693CodecTask, - .ApplicationInitFunc = ApplicationInitDummy, - .ApplicationResetFunc = ApplicationResetDummy, - .ApplicationTaskFunc = ApplicationTaskDummy, - .ApplicationTickFunc = ApplicationTickDummy, - .ApplicationProcessFunc = ApplicationProcessDummy, + .CodecInitFunc = SniffISO15693CodecInit, + .CodecDeInitFunc = SniffISO15693CodecDeInit, + .CodecTaskFunc = SniffISO15693CodecTask, + .ApplicationInitFunc = SniffISO15693AppInit, + .ApplicationResetFunc = SniffISO15693AppReset, + .ApplicationTaskFunc = SniffISO15693AppTask, + .ApplicationTickFunc = SniffISO15693AppTick, + .ApplicationProcessFunc = SniffISO15693AppProcess, .ApplicationGetUidFunc = ApplicationGetUidDummy, .ApplicationSetUidFunc = ApplicationSetUidDummy, .UidSize = 0, @@ -419,6 +481,24 @@ static const PROGMEM ConfigurationType ConfigurationTable[] = { .TagFamily = TAG_FAMILY_ISO15693 }, #endif +#ifdef CONFIG_NTAG215_SUPPORT + [CONFIG_NTAG215] = { + .CodecInitFunc = ISO14443ACodecInit, + .CodecDeInitFunc = ISO14443ACodecDeInit, + .CodecTaskFunc = ISO14443ACodecTask, + .ApplicationInitFunc = NTAG215AppInit, + .ApplicationResetFunc = NTAG215AppReset, + .ApplicationTaskFunc = NTAG215AppTask, + .ApplicationTickFunc = ApplicationTickDummy, + .ApplicationProcessFunc = NTAG215AppProcess, + .ApplicationGetUidFunc = NTAG215GetUid, + .ApplicationSetUidFunc = NTAG215SetUid, + .UidSize = NTAG215_UID_SIZE, + .MemorySize = NTAG215_MEM_SIZE, + .ReadOnly = false, + .TagFamily = TAG_FAMILY_ISO14443A + }, +#endif }; ConfigurationType ActiveConfiguration; @@ -446,10 +526,14 @@ void ConfigurationSetById(ConfigurationEnum Configuration) { &ConfigurationTable[Configuration], sizeof(ConfigurationType)); // Configure antenna load as appropriate +#ifdef CONFIG_ISO14443A_READER_SUPPORT if (Configuration == CONFIG_ISO14443A_READER) PORTC.OUTCLR = PIN7_bm; else PORTC.OUTSET = PIN7_bm; +#else + PORTC.OUTSET = PIN7_bm; +#endif CodecInit(); ApplicationInit(); @@ -464,7 +548,11 @@ bool ConfigurationSetByName(const char *Configuration) { if (MapTextToId(ConfigurationMap, ARRAY_COUNT(ConfigurationMap), Configuration, &Id)) { // The last configuration can only be configured as a reader +#ifdef CONFIG_ISO14443A_READER_SUPPORT if (GlobalSettings.ActiveSettingIdx >= SETTINGS_COUNT && Id != CONFIG_ISO14443A_READER) { +#else + if (GlobalSettings.ActiveSettingIdx >= SETTINGS_COUNT) { +#endif return false; } ConfigurationSetById(Id); diff --git a/Firmware/Chameleon-Mini/Configuration.h b/Firmware/Chameleon-Mini/Configuration.h index fefa9fe..0cfd264 100644 --- a/Firmware/Chameleon-Mini/Configuration.h +++ b/Firmware/Chameleon-Mini/Configuration.h @@ -13,6 +13,10 @@ #define CONFIGURATION_NAME_LENGTH_MAX 32 #define CONFIGURATION_UID_SIZE_MAX 16 +/* ATQA & SAK */ +#define CONFIGURATION_DUMMY_ATQA 0x0000 +#define CONFIGURATION_DUMMY_SAK 0x00 + typedef uint8_t ConfigurationUidType[CONFIGURATION_UID_SIZE_MAX]; @@ -53,6 +57,9 @@ typedef enum { #ifdef CONFIG_ISO14443A_READER_SUPPORT CONFIG_ISO14443A_READER, #endif +#ifdef CONFIG_NTAG215_SUPPORT + CONFIG_NTAG215, +#endif #ifdef CONFIG_VICINITY_SUPPORT CONFIG_VICINITY, #endif @@ -135,6 +142,26 @@ typedef struct { * \param Uid The source buffer. */ void (*ApplicationSetUidFunc)(ConfigurationUidType Uid); + /** + * Writes the SAK for the current configuration to the given buffer. + * \param Sak The target buffer. + */ + void (*ApplicationGetSakFunc) (uint8_t * Sak); + /** + * Writes a given SAK to the current configuration. + * \param Sak The source buffer. + */ + void (*ApplicationSetSakFunc) (uint8_t Sak); + /** + * Writes the ATQA for the current configuration to the given buffer. + * \param Atqa The target buffer. + */ + void (*ApplicationGetAtqaFunc) (uint16_t * Atqa); + /** + * Writes a given ATQA to the current configuration. + * \param Atqa The source buffer. + */ + void (*ApplicationSetAtqaFunc) (uint16_t Atqa); /** * @} */ diff --git a/Firmware/Chameleon-Mini/ISRSharing.S b/Firmware/Chameleon-Mini/ISRSharing.S new file mode 100644 index 0000000..a1d197c --- /dev/null +++ b/Firmware/Chameleon-Mini/ISRSharing.S @@ -0,0 +1,65 @@ +; This file allows to share ISR at runtime with a low overhead. +; Functions that have to be shared MUST be defined as ISR_SHARED types +; (Defined in Common.h) to instruct GCC to compile them preserving the stack +; +; Created on: 2019-12-06 +; Author: ceres-c +; + +#include "Codec/Codec.h" +#include + +; Macro to call ISR functions +; For development purposes: cycles penality before landing in the Shared ISR is 10 +.macro call_isr isr_address +push r30 ; 1 +push r31 ; 1 +lds r30, \isr_address ; 3 +lds r31, \isr_address+1 ; 3 +icall ; 2 (16 bit PC) +pop r31 +pop r30 +reti +.endm + +; Shared ISR must be defined below as globals +; Example: +; +; .global +; INTERRUPT-TO-SHARE: +; call_isr POINTER-TO-VARIABLE-INTERRUPT-HANDLER +; +; Where: +; INTERRUPT-TO-SHARE +; The target interrupt which needs to be shared +; POINTER-TO-VARIABLE-INTERRUPT-HANDLER +; A pointer to the interrupt handler which can be modified at runtime. +; Must be a volatile pointer to function declared in Codec.h + +.global CODEC_DEMOD_IN_INT0_VECT +CODEC_DEMOD_IN_INT0_VECT: + call_isr isr_func_CODEC_DEMOD_IN_INT0_VECT + +.global CODEC_TIMER_SAMPLING_CCC_VECT +CODEC_TIMER_SAMPLING_CCC_VECT: + call_isr isr_func_TCD0_CCC_vect + +.global CODEC_TIMER_LOADMOD_OVF_VECT +CODEC_TIMER_LOADMOD_OVF_VECT: + call_isr isr_func_CODEC_TIMER_LOADMOD_OVF_VECT + +.global CODEC_TIMER_LOADMOD_CCA_VECT +CODEC_TIMER_LOADMOD_CCA_VECT: + call_isr isr_func_CODEC_TIMER_LOADMOD_CCA_VECT + +.global CODEC_TIMER_LOADMOD_CCB_VECT +CODEC_TIMER_LOADMOD_CCB_VECT: + call_isr isr_func_CODEC_TIMER_LOADMOD_CCB_VECT + +.global CODEC_TIMER_TIMESTAMPS_CCA_VECT +CODEC_TIMER_TIMESTAMPS_CCA_VECT: + call_isr isr_func_CODEC_TIMER_TIMESTAMPS_CCA_VECT + +.global ACA_AC0_vect +ACA_AC0_vect: + call_isr isr_func_ACA_AC0_vect diff --git a/Firmware/Chameleon-Mini/Makefile b/Firmware/Chameleon-Mini/Makefile index b680af4..9190350 100644 --- a/Firmware/Chameleon-Mini/Makefile +++ b/Firmware/Chameleon-Mini/Makefile @@ -15,13 +15,14 @@ SETTINGS += -DCONFIG_MF_CLASSIC_1K_7B_SUPPORT SETTINGS += -DCONFIG_MF_CLASSIC_4K_SUPPORT SETTINGS += -DCONFIG_MF_CLASSIC_4K_7B_SUPPORT SETTINGS += -DCONFIG_MF_ULTRALIGHT_SUPPORT -SETTINGS += -DCONFIG_ISO14443A_SNIFF_SUPPORT -SETTINGS += -DCONFIG_ISO14443A_READER_SUPPORT -SETTINGS += -DCONFIG_VICINITY_SUPPORT -SETTINGS += -DCONFIG_SL2S2002_SUPPORT -SETTINGS += -DCONFIG_TITAGITSTANDARD_SUPPORT -SETTINGS += -DCONFIG_ISO15693_SNIFF_SUPPORT -SETTINGS += -DCONFIG_EM4233_SUPPORT +SETTINGS += -DCONFIG_ISO14443A_SNIFF_SUPPORT +SETTINGS += -DCONFIG_ISO14443A_READER_SUPPORT +SETTINGS += -DCONFIG_NTAG215_SUPPORT +# SETTINGS += -DCONFIG_VICINITY_SUPPORT +# SETTINGS += -DCONFIG_SL2S2002_SUPPORT +# SETTINGS += -DCONFIG_TITAGITSTANDARD_SUPPORT +# SETTINGS += -DCONFIG_EM4233_SUPPORT +SETTINGS += -DCONFIG_ISO15693_SNIFF_SUPPORT #Support magic mode on mifare classic configuration SETTINGS += -DSUPPORT_MF_CLASSIC_MAGIC_MODE @@ -108,15 +109,15 @@ F_CPU = 27120000 F_USB = 48000000 TARGET = Chameleon-RevG OPTIMIZATION = s -SRC += Chameleon-Mini.c LUFADescriptors.c System.c Configuration.c Random.c Common.c Memory.c MemoryAsm.S Button.c Log.c Settings.c LED.c Map.c AntennaLevel.c Uart.c uartcmd.c +SRC += Chameleon-Mini.c LUFADescriptors.c System.c ISRSharing.S Configuration.c Random.c Common.c Memory.c MemoryAsm.S Button.c Log.c Settings.c LED.c Map.c AntennaLevel.c Uart.c uartcmd.c SRC += Terminal/Terminal.c Terminal/Commands.c Terminal/XModem.c Terminal/CommandLine.c SRC += Codec/Codec.c Codec/ISO14443-2A.c Codec/Reader14443-2A.c Codec/SniffISO14443-2A.c Codec/Reader14443-ISR.S -SRC += Application/MifareUltralight.c Application/MifareClassic.c Application/ISO14443-3A.c Application/Crypto1.c Application/Reader14443A.c Application/Sniff14443A.c Application/CryptoTDEA.S -SRC += Codec/ISO15693.c -SRC += Application/Vicinity.c Application/Sl2s2002.c Application/TITagitstandard.c Application/ISO15693-A.c Application/EM4233.c +SRC += Application/MifareUltralight.c Application/MifareClassic.c Application/ISO14443-3A.c Application/Crypto1.c Application/Reader14443A.c Application/Sniff14443A.c Application/CryptoTDEA.S Application/NTAG215.c +SRC += Codec/ISO15693.c Codec/SniffISO15693.c +SRC += Application/Vicinity.c Application/Sl2s2002.c Application/TITagitstandard.c Application/ISO15693-A.c Application/EM4233.c Application/Sniff15693.c SRC += $(LUFA_SRC_USB) $(LUFA_SRC_USBCLASS) LUFA_PATH = ../LUFA -CC_FLAGS = -DUSE_LUFA_CONFIG_HEADER -DFLASH_DATA_ADDR=$(FLASH_DATA_ADDR) -DFLASH_DATA_SIZE=$(FLASH_DATA_SIZE) -DSPM_HELPER_ADDR=$(SPM_HELPER_ADDR) -DBUILD_DATE=$(BUILD_DATE) -DCOMMIT_ID=\"$(COMMIT_ID)\" $(SETTINGS) +CC_FLAGS = -flto -DUSE_LUFA_CONFIG_HEADER -DFLASH_DATA_ADDR=$(FLASH_DATA_ADDR) -DFLASH_DATA_SIZE=$(FLASH_DATA_SIZE) -DSPM_HELPER_ADDR=$(SPM_HELPER_ADDR) -DBUILD_DATE=$(BUILD_DATE) -DCOMMIT_ID=\"$(COMMIT_ID)\" $(SETTINGS) LD_FLAGS = -Wl,--section-start=.flashdata=$(FLASH_DATA_ADDR) -Wl,--section-start=.spmhelper=$(SPM_HELPER_ADDR) OBJDIR = Bin OBJECT_FILES = @@ -134,6 +135,7 @@ AVRDUDE_FLAGS = -p $(AVRDUDE_MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) AVRDUDE_WRITE_APP_LATEST = -U application:w:Latest/Chameleon-RevG.hex AVRDUDE_WRITE_EEPROM_LATEST = -U eeprom:w:Latest/Chameleon-RevG.eep +.PHONY: program program-latest dfu-flip dfu-prog dfu-reset check_size style # Default target all: @@ -165,27 +167,45 @@ spmhelper: $(TARGET).elf $(CROSS)-objcopy -O ihex -j .spmhelper $(SPM_HELPER_OBJCOPY) $(TARGET).elf $(TARGET).hex # Program the device using avrdude -program: $(TARGET).hex $(TARGET).eep +program: $(TARGET).hex $(TARGET).eep check_size avrdude $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_APP) $(AVRDUDE_WRITE_EEPROM) # Program the device using avrdude with the latest official firmware -program-latest: +program-latest: check_size avrdude $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_APP_LATEST) $(AVRDUDE_WRITE_EEPROM_LATEST) # Program the device using batchisp and the DFU bootloader # Note that the device has to be in bootloader mode already -dfu-flip: $(TARGET).hex $(TARGET).eep +dfu-flip: $(TARGET).hex $(TARGET).eep check_size cp $(TARGET).eep EEPROM.hex batchisp -hardware usb -device $(MCU) -operation erase f memory FLASH loadbuffer $(TARGET).hex program verify memory EEPROM loadbuffer EEPROM.hex program verify start reset 0 rm EEPROM.hex # Program the device using dfu-programmer -dfu-prog: $(TARGET).hex $(TARGET).eep +dfu-prog: $(TARGET).hex $(TARGET).eep check_size dfu-programmer $(MCU) erase dfu-programmer $(MCU) flash-eeprom $(TARGET).eep dfu-programmer $(MCU) flash $(TARGET).hex dfu-programmer $(MCU) reset +# Reset the device after firmware upgrade +dfu-reset: + dfu-programmer $(MCU) reset + +check_size: + @{ \ + set -e; \ + if [ ! -f $(TARGET).elf ]; then \ + exit 0; \ + fi; \ + PROGMEM_SIZE=$$(avr-size --mcu=atxmega128a4u --format=avr $(TARGET).elf | grep -oP "Program:\W+(\d+)" | grep -oP "\d+"); \ + MAX_PRGMEM_SIZE=$$(printf "%d\n" $(FLASH_DATA_ADDR)); \ + if [ $$PROGMEM_SIZE -gt $$MAX_PRGMEM_SIZE ]; then \ + echo "make: *** $(TARGET).elf Application Section size $$PROGMEM_SIZE excedes maximum allowed $$MAX_PRGMEM_SIZE. Please disable some features in Makefile"; \ + exit 1; \ + fi; \ + } + style: # Make sure astyle is installed @which astyle >/dev/null || ( echo "Please install 'astyle' package first" ; exit 1 ) diff --git a/Firmware/Chameleon-Mini/Memory.c b/Firmware/Chameleon-Mini/Memory.c index 3ea60ab..c950e83 100644 --- a/Firmware/Chameleon-Mini/Memory.c +++ b/Firmware/Chameleon-Mini/Memory.c @@ -172,10 +172,15 @@ INLINE void FRAMWrite(const void *Buffer, uint16_t Address, uint16_t ByteCount) SPIWriteBlock(Buffer, ByteCount); FRAM_PORT.OUTSET = FRAM_CS; - +#ifdef CONFIG_ISO14443A_READER_SUPPORT if (0 == Address && GlobalSettings.ActiveSettingPtr->Configuration != CONFIG_ISO14443A_READER) { ConfigurationSetById(GlobalSettings.ActiveSettingPtr->Configuration); } +#else + if (0 == Address) { + ConfigurationSetById(GlobalSettings.ActiveSettingPtr->Configuration); + } +#endif } INLINE void FlashRead(void *Buffer, uint32_t Address, uint16_t ByteCount) { @@ -421,8 +426,10 @@ void MemoryRecall(void) { if (GlobalSettings.ActiveSettingIdx < SETTINGS_COUNT) FlashToFRAM((uint32_t) GlobalSettings.ActiveSettingIdx * MEMORY_SIZE_PER_SETTING, ActiveConfiguration.MemorySize); +#ifdef CONFIG_ISO14443A_READER_SUPPORT if (GlobalSettings.ActiveSettingPtr->Configuration != CONFIG_ISO14443A_READER) ActiveConfiguration.ApplicationInitFunc(); +#endif SystemTickClearFlag(); } diff --git a/Firmware/Chameleon-Mini/Settings.c b/Firmware/Chameleon-Mini/Settings.c index 290806c..dc557bc 100644 --- a/Firmware/Chameleon-Mini/Settings.c +++ b/Firmware/Chameleon-Mini/Settings.c @@ -39,7 +39,9 @@ SettingsType EEMEM StoredSettings = { .PendingTaskTimeout = DEFAULT_PENDING_TASK_TIMEOUT, .ReaderThreshold = DEFAULT_READER_THRESHOLD, .bSakMode = 0, - }, + } +#ifdef CONFIG_ISO14443A_READER_SUPPORT + , // In the last configuration, there is no storage space. This is used as the card reader mode only [SETTINGS_COUNT] = { @@ -55,6 +57,7 @@ SettingsType EEMEM StoredSettings = { .ReaderThreshold = DEFAULT_READER_THRESHOLD, .bSakMode = 0, } +#endif } }; @@ -77,7 +80,9 @@ void SettingsLoad(void) { GlobalSettings.Settings[i].PendingTaskTimeout = DEFAULT_PENDING_TASK_TIMEOUT; GlobalSettings.Settings[i].ReaderThreshold = DEFAULT_READER_THRESHOLD; } +#ifdef CONFIG_ISO14443A_READER_SUPPORT GlobalSettings.Settings[SETTINGS_COUNT].Configuration = CONFIG_ISO14443A_READER; +#endif SettingsSave(); } diff --git a/Firmware/Chameleon-Mini/System.c b/Firmware/Chameleon-Mini/System.c index a2b33ca..30899dd 100644 --- a/Firmware/Chameleon-Mini/System.c +++ b/Firmware/Chameleon-Mini/System.c @@ -151,4 +151,3 @@ void SystemInterruptInit(void) { PMIC.CTRL = PMIC_LOLVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_HILVLEN_bm; sei(); } - diff --git a/Firmware/Chameleon-Mini/Terminal/CommandLine.c b/Firmware/Chameleon-Mini/Terminal/CommandLine.c index 1f1e4d5..0e6e101 100644 --- a/Firmware/Chameleon-Mini/Terminal/CommandLine.c +++ b/Firmware/Chameleon-Mini/Terminal/CommandLine.c @@ -61,6 +61,20 @@ const PROGMEM CommandEntryType CommandTable[] = { .SetFunc = CommandSetUid, .GetFunc = CommandGetUid }, + { + .Command = COMMAND_ATQA, + .ExecFunc = NO_FUNCTION, + .ExecParamFunc = NO_FUNCTION, + .SetFunc = CommandSetAtqa, + .GetFunc = CommandGetAtqa + }, + { + .Command = COMMAND_SAK, + .ExecFunc = NO_FUNCTION, + .ExecParamFunc = NO_FUNCTION, + .SetFunc = CommandSetSak, + .GetFunc = CommandGetSak + }, { .Command = COMMAND_READONLY, .ExecFunc = NO_FUNCTION, @@ -246,6 +260,7 @@ const PROGMEM CommandEntryType CommandTable[] = { .SetFunc = NO_FUNCTION, .GetFunc = CommandGetSysTick }, +#ifdef CONFIG_ISO14443A_READER_SUPPORT { .Command = COMMAND_SEND_RAW, .ExecFunc = NO_FUNCTION, @@ -274,6 +289,13 @@ const PROGMEM CommandEntryType CommandTable[] = { .SetFunc = NO_FUNCTION, .GetFunc = NO_FUNCTION }, + { + .Command = COMMAND_CLONE_MFU, + .ExecFunc = CommandExecCloneMFU, + .ExecParamFunc = NO_FUNCTION, + .SetFunc = NO_FUNCTION, + .GetFunc = NO_FUNCTION + }, { .Command = COMMAND_IDENTIFY_CARD, .ExecFunc = CommandExecIdentifyCard, @@ -281,6 +303,7 @@ const PROGMEM CommandEntryType CommandTable[] = { .SetFunc = NO_FUNCTION, .GetFunc = NO_FUNCTION }, +#endif { .Command = COMMAND_TIMEOUT, .ExecFunc = NO_FUNCTION, @@ -309,6 +332,7 @@ const PROGMEM CommandEntryType CommandTable[] = { .SetFunc = CommandSetField, .GetFunc = CommandGetField }, +#ifdef CONFIG_ISO14443A_READER_SUPPORT { .Command = COMMAND_CLONE, .ExecFunc = CommandExecClone, @@ -316,6 +340,7 @@ const PROGMEM CommandEntryType CommandTable[] = { .SetFunc = NO_FUNCTION, .GetFunc = NO_FUNCTION }, +#endif { .Command = COMMAND_SETUIDMODE, .ExecFunc = NO_FUNCTION, diff --git a/Firmware/Chameleon-Mini/Terminal/Commands.c b/Firmware/Chameleon-Mini/Terminal/Commands.c index 002788b..3ace97b 100644 --- a/Firmware/Chameleon-Mini/Terminal/Commands.c +++ b/Firmware/Chameleon-Mini/Terminal/Commands.c @@ -16,6 +16,7 @@ #include "../Battery.h" #include "../Codec/Codec.h" #include "uartcmd.h" +#include "../Application/Reader14443A.h" extern Reader14443Command Reader14443CurrentCommand; extern Sniff14443Command Sniff14443CurrentCommand; @@ -51,6 +52,63 @@ CommandStatusIdType CommandSetConfig(char *OutMessage, const char *InParam) { } } +CommandStatusIdType CommandGetAtqa(char* OutParam) { + uint16_t Atqa; + + ApplicationGetAtqa(&Atqa); + + // Convert uint16 to uint8 buffer[] + uint8_t atqaBuffer[2] = { 0,0 }; + atqaBuffer[1] = (uint8_t)Atqa; + atqaBuffer[0] = Atqa >> 8; + + BufferToHexString(OutParam, TERMINAL_BUFFER_SIZE, &atqaBuffer, sizeof(uint16_t)); + + return COMMAND_INFO_OK_WITH_TEXT_ID; +} + +CommandStatusIdType CommandSetAtqa(char* OutMessage, const char* InParam) { + uint8_t AtqaBuffer[2] = { 0, 0 }; + uint16_t Atqa = 0; + + if (HexStringToBuffer(&AtqaBuffer, sizeof(AtqaBuffer), InParam) != sizeof(uint16_t)) { + // This has to be 4 digits (2 bytes), e.g.: 0004 + return COMMAND_ERR_INVALID_PARAM_ID; + } + + // Convert uint8 buffer[] to uint16 + if (strlen(InParam) > 2) { + Atqa = ((uint16_t)AtqaBuffer[0] << 8) | AtqaBuffer[1]; + } + else { + Atqa = AtqaBuffer[0]; + } + + ApplicationSetAtqa(Atqa); + return COMMAND_INFO_OK_ID; +} + +CommandStatusIdType CommandGetSak(char* OutParam) { + uint8_t Sak; + + ApplicationGetSak(&Sak); + + BufferToHexString(OutParam, TERMINAL_BUFFER_SIZE, &Sak, sizeof(uint8_t)); + return COMMAND_INFO_OK_WITH_TEXT_ID; +} + +CommandStatusIdType CommandSetSak(char* OutMessage, const char* InParam) { + uint8_t Sak; + + if (HexStringToBuffer(&Sak, sizeof(uint8_t), InParam) != sizeof(uint8_t)) { + // This has to be 2 digits (1 byte), e.g.: 04 + return COMMAND_ERR_INVALID_PARAM_ID; + } + + ApplicationSetSak(Sak); + return COMMAND_INFO_OK_ID; +} + CommandStatusIdType CommandGetUid(char *OutParam) { uint8_t UidBuffer[COMMAND_UID_BUFSIZE]; uint16_t UidSize = ActiveConfiguration.UidSize; @@ -400,6 +458,7 @@ CommandStatusIdType CommandGetSysTick(char *OutParam) { return COMMAND_INFO_OK_WITH_TEXT_ID; } +#ifdef CONFIG_ISO14443A_READER_SUPPORT CommandStatusIdType CommandExecParamSend(char *OutMessage, const char *InParams) { if (GlobalSettings.ActiveSettingPtr->Configuration != CONFIG_ISO14443A_READER) return COMMAND_ERR_INVALID_USAGE_ID; @@ -491,6 +550,17 @@ CommandStatusIdType CommandExecDumpMFU(char *OutMessage) { return TIMEOUT_COMMAND; } +CommandStatusIdType CommandExecCloneMFU(char *OutMessage) { + ConfigurationSetById(CONFIG_ISO14443A_READER); + ApplicationReset(); + + Reader14443CurrentCommand = Reader14443_Clone_MF_Ultralight; + Reader14443AAppInit(); + Reader14443ACodecStart(); + CommandLinePendingTaskTimeout = &Reader14443AAppTimeout; + return TIMEOUT_COMMAND; +} + CommandStatusIdType CommandExecGetUid(char *OutMessage) { // this function is for reading the uid in reader mode if (GlobalSettings.ActiveSettingPtr->Configuration != CONFIG_ISO14443A_READER) return COMMAND_ERR_INVALID_USAGE_ID; @@ -514,6 +584,7 @@ CommandStatusIdType CommandExecIdentifyCard(char *OutMessage) { CommandLinePendingTaskTimeout = &Reader14443AAppTimeout; return TIMEOUT_COMMAND; } +#endif CommandStatusIdType CommandGetTimeout(char *OutParam) { snprintf_P(OutParam, TERMINAL_BUFFER_SIZE, PSTR("%u ms"), GlobalSettings.ActiveSettingPtr->PendingTaskTimeout * 100); @@ -578,6 +649,7 @@ CommandStatusIdType CommandGetField(char *OutMessage) { CommandStatusIdType CommandExecAutocalibrate(char *OutMessage) { +#ifdef CONFIG_ISO14443A_READER_SUPPORT if (GlobalSettings.ActiveSettingPtr->Configuration == CONFIG_ISO14443A_READER) { ApplicationReset(); @@ -586,19 +658,22 @@ CommandStatusIdType CommandExecAutocalibrate(char *OutMessage) { Reader14443ACodecStart(); CommandLinePendingTaskTimeout = &Reader14443AAppTimeout; return TIMEOUT_COMMAND; - } else if (GlobalSettings.ActiveSettingPtr->Configuration == CONFIG_ISO14443A_SNIFF) { + } +#endif +#ifdef CONFIG_ISO14443A_SNIFF_SUPPORT + if (GlobalSettings.ActiveSettingPtr->Configuration == CONFIG_ISO14443A_SNIFF) { ApplicationReset(); Sniff14443CurrentCommand = Sniff14443_Autocalibrate; Sniff14443AAppInit(); CommandLinePendingTaskTimeout = &Sniff14443AAppTimeout; return TIMEOUT_COMMAND; - } else { - return COMMAND_ERR_INVALID_USAGE_ID; } - +#endif + return COMMAND_ERR_INVALID_USAGE_ID; } +#ifdef CONFIG_ISO14443A_READER_SUPPORT CommandStatusIdType CommandExecClone(char *OutMessage) { ConfigurationSetById(CONFIG_ISO14443A_READER); @@ -611,6 +686,7 @@ CommandStatusIdType CommandExecClone(char *OutMessage) { return TIMEOUT_COMMAND; } +#endif extern uint32_t dwBaudRate; CommandStatusIdType CommandGetBaudrate(char *OutParam) { diff --git a/Firmware/Chameleon-Mini/Terminal/Commands.h b/Firmware/Chameleon-Mini/Terminal/Commands.h index f4eebe0..8593714 100644 --- a/Firmware/Chameleon-Mini/Terminal/Commands.h +++ b/Firmware/Chameleon-Mini/Terminal/Commands.h @@ -66,6 +66,14 @@ CommandStatusIdType CommandSetConfig(char *OutMessage, const char *InParam); CommandStatusIdType CommandGetUid(char *OutParam); CommandStatusIdType CommandSetUid(char *OutMessage, const char *InParam); +#define COMMAND_ATQA "ATQA" +CommandStatusIdType CommandGetAtqa(char* OutParam); +CommandStatusIdType CommandSetAtqa(char* OutMessage, const char* InParam); + +#define COMMAND_SAK "SAK" +CommandStatusIdType CommandGetSak(char* OutParam); +CommandStatusIdType CommandSetSak(char* OutMessage, const char* InParam); + #define COMMAND_READONLY "READONLY" CommandStatusIdType CommandGetReadOnly(char *OutParam); CommandStatusIdType CommandSetReadOnly(char *OutMessage, const char *InParam); @@ -166,6 +174,9 @@ CommandStatusIdType CommandExecGetUid(char *OutMessage); #define COMMAND_DUMP_MFU "DUMP_MFU" CommandStatusIdType CommandExecDumpMFU(char *OutMessage); +#define COMMAND_CLONE_MFU "CLONE_MFU" +CommandStatusIdType CommandExecCloneMFU(char *OutMessage); + #define COMMAND_IDENTIFY_CARD "IDENTIFY" CommandStatusIdType CommandExecIdentifyCard(char *OutMessage); diff --git a/README.md b/README.md index 485485e..f3acdd9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ Chameleon-Mini ============== This is NOT the official repository of ChameleonMini, a freely programmable, portable tool for NFC security analysis that can emulate and clone contactless cards, read RFID tags and sniff/log RF data. +Chameleon BLE API +[**Codes.RAR**](https://github.com/RfidResearchGroup/ChameleonBLEAPI/blob/master/code.rar) + Our Project is based on the open-source project by ChameleonMini RevG by Kasper & Oswald [Webshop](https://shop.kasper.it/). Our ChameleonMini / ChameleonTiny by ProxGrind is available on Indiegogo now. @@ -18,9 +21,11 @@ Our US Reseller Most information can be found on **[Here](http://chameleontiny.com/)** -Our APP can be found here: **[Here](https://play.google.com/store/apps/details?id=com.proxgrind.chameleon)** +Our Android APP can be found here: **[Here](https://play.google.com/store/apps/details?id=com.proxgrind.chameleon)** -Dropbox download for all relevant files: +[Bettse](https://github.com/bettse) wrote an iOS app: **[ChameleonTiny Manager](https://apps.apple.com/us/app/chameleontiny-manager/id1601341565)** + +Dropbox download for all relevant files: [Link-Broken] ---------------------------------------- **[Here](https://www.dropbox.com/s/xt2o9ugalaam4xu/REV.G%20User%20Manual%2020200309.zip?dl=0)** **Dropbox** @@ -70,21 +75,25 @@ B. Quick Crash Course **1.Detect keys and upload card files** -(1) Prepare the computer GUI or Android APP. +(1) Prepare the computer GUI, Android APP, or iOS App:   Computer GUI download address: **[Here](http://www.icesql.se/download/ChameleonMiniGUI/publish.htm)** Source: **[Here](https://github.com/iceman1001/ChameleonMini-rebootedGUI)** - Android APP download address: +Android APP download address: Google Store:**[Here](https://play.google.com/store/apps/details?id=com.proxgrind.chameleon)** +iOS App download address: + +Apple App Store: **[Here](https://apps.apple.com/us/app/chameleontiny-manager/id1601341565)** + **Password: e4g1** (2) Connect the Chameleon MINI or TINY using the Android APP. -USB port direct connection: Both the Chameleon MINI and TINY support direct connection to the mobile phone USB port. For the MINI, an additional OTG adapter needs to be purchased. TINY uses its own dual-headed TYPEC data cable to connect directly to TYPEC mobile phones. +USB port direct connection: Both the Chameleon MINI and TINY support direct connection to the mobile phone USB port. For the MINI, an additional OTG adapter needs to be purchased. TINY uses its own dual-headed TYPE-C data cable to connect directly to TYPE-C mobile phones. Bluetooth connection: Chameleon MINI has built-in Bluetooth BLE4.0. Press any button first to wake up Bluetooth. Turn on Bluetooth on your Android phone and the app will automatically connect. (3) Use Android APP to enable detection mode. @@ -106,9 +115,9 @@ Note: Multiple red LEDs are on at the same time during detection, which means th (5) Use Android APP to import existing card data files in batches.  Use QQ to send the card data file to the mobile phone QQ, or connect the mobile phone to the computer and transfer the file to any directory on the mobile phone. -Open the app, click the "DUMP" column below, click the "SCanner" in the "plus sign" in the upper right corner, click the three horizontal line buttons in the upper left corner, and select this phone. Then select the root directory of the QQ receiving file or the previously copied directory, and click Allow Access. All card data files will be automatically scanned into the "DUMP file" interface, which can be uploaded or edited at will. +Open the app, click the "DUMP" column below, click the "Scanner" in the "plus sign" in the upper right corner, click the three horizontal line buttons in the upper left corner, and select this phone. Then select the root directory of the QQ receiving file or the previously copied directory, and click Allow Access. All card data files will be automatically scanned into the "DUMP file" interface, which can be uploaded or edited at will. -Click the card data file in the “DUM” column below, and click “Upload” below to upload to the card slot corresponding to the chameleon. +Click the card data file in the “DUMP” column below, and click “Upload” below to upload to the card slot corresponding to the chameleon. **2. Introduction to UID mode and SAK mode** @@ -150,7 +159,7 @@ MF_DETECTION_1K|Detecting reader to obtain keys|MFKEY32V2|List results directly| MF_DETECTION_4K|Detecting reader to obtain keys|MFKEY32V2|List results directly| ISO14443A_READER|Reader Mode|-|Display UID| ISO14443A_SNIFF|Sniffing|-|Not supported| -ISO15693_SNIFF|Sniffing|-|Not supported| +ISO15693_SNIFF|Sniffing|-|Supported| **4. Button Custom Function Introduction** @@ -199,9 +208,9 @@ In the closed state, press and hold the yellow button near the USB, and then let (2 ) ChameleonTiny Turn on the chameleon: -In the closed state, press the A button once to turn on the chameleon power. The red LED lights up to indicate the slot number. After 5 seconds without operation, it will automatically sleep and shut down. +In the off/standby state, press the A button once to turn on the chameleon power. The red LED lights up to indicate the slot number. After 5 seconds without operation, it will automatically sleep and shut down. This can be adjusted in Android Application. DFU firmware upgrade status: -In the off state, hold down the B button and connect the USB. You can enter the DFU flash firmware mode. +In the off state, hold down the B button and connect the USB. You can enter the DFU mode to flash new firmware . **Appendix** ================= @@ -287,7 +296,8 @@ Sniff Mode NTAG|ISO14443A 106 kbit/s|Support|Support| No LEGIC prime|LEGICprime/ ISO14443A/ ISO15693|Possible but not supported|Possible but not supported|No| HID iCLASS|125kHz/ISO15693/ISO14443B|Possible but not supported|Possible but not supported|No| Epass|ISO14443A/B|Supported / Supported|Low rate only / not supported|No| -ISO15693|ISO15693|Support|Support|No| +TiTagIT Standard|ISO15693|Support|Support|Support| +EM4233|ISO15693|Support|Support|Support| (2)Sniff Mode Support Type @@ -296,7 +306,8 @@ ISO15693|ISO15693|Support|Support|No| |Encoding type|Whether the hardware supports|Does the software support| Whether the application layer supports|Note| | ------------------- |:-------------------:| -------------------:| ------------------- |-------------------:| Non-13.56MHz|Not Supported|Not Supported|Not Supported| -ISO 14443 A 106 kbit/s|Reader -> card Direction sniffing|Maybe support the other direction|Currently only supported Reader -> card Direction sniffing |Support| +ISO 14443 A 106 kbit/s|Reader -> card Direction sniffing|Maybe support the other direction|Currently only supported Reader -> card Direction sniffing || +ISO 15693|Support|Support|Support|Single subcarrier only| (3) Card Type Supported via Reading diff --git a/Software/Chameleon/Device.py b/Software/Chameleon/Device.py index 6aae7d5..4629fcb 100644 --- a/Software/Chameleon/Device.py +++ b/Software/Chameleon/Device.py @@ -28,6 +28,7 @@ class Device: COMMAND_RED_LED = "LEDRED" COMMAND_THRESHOLD = "THRESHOLD" COMMAND_UPGRADE = "upgrade" + COMMAND_CLEAR = "CLEAR" STATUS_CODE_OK = 100 STATUS_CODE_OK_WITH_TEXT = 101 @@ -207,6 +208,9 @@ class Device: def cmdClearLog(self): return self.execCmd(self.COMMAND_LOG_CLEAR) + + def cmdClear(self): + return self.execCmd(self.COMMAND_CLEAR) def cmdLogMode(self, newLogMode): return self.getSetCmd(self.COMMAND_LOGMODE, newLogMode) diff --git a/Software/Chameleon/Log.py b/Software/Chameleon/Log.py index 524ecab..f26d404 100644 --- a/Software/Chameleon/Log.py +++ b/Software/Chameleon/Log.py @@ -71,7 +71,8 @@ eventTypes = { 0x45: { 'name': 'CODEC RX SNI READER W/PARITY', 'decoder': binaryParityDecoder }, 0x46: { 'name': 'CODEC RX SNI CARD', 'decoder': binaryDecoder }, 0x47: { 'name': 'CODEC RX SNI CARD W/PARITY', 'decoder': binaryParityDecoder }, - + 0x48: { 'name': 'CODEC RX SNI READER FIELD DETECTED', 'decoder': noDecoder }, + 0x80: { 'name': 'APP READ', 'decoder': binaryDecoder }, 0x81: { 'name': 'APP WRITE', 'decoder': binaryDecoder }, @@ -95,7 +96,7 @@ eventTypes = { } TIMESTAMP_MAX = 65536 -eventTypes = { i : ({'name': 'UNKNOWN', 'decoder': binaryDecoder} if i not in eventTypes.keys() else eventTypes[i]) for i in range(256) } +eventTypes = { i : ({'name': f'UNKNOWN {hex(i)}', 'decoder': binaryDecoder} if i not in eventTypes.keys() else eventTypes[i]) for i in range(256) } def parseBinary(binaryStream, decoder=None): log = [] @@ -130,11 +131,11 @@ def parseBinary(binaryStream, decoder=None): logData = eventTypes[event]['decoder'](logData) # Calculate delta timestamp respecting 16 bit overflow - deltaTimestamp = timestamp - lastTimestamp; + deltaTimestamp = timestamp - lastTimestamp lastTimestamp = timestamp if (deltaTimestamp < 0): - deltaTimestamp += TIMESTAMP_MAX; + deltaTimestamp += TIMESTAMP_MAX note = "" # If we need to decode the data and paritybit check success diff --git a/Software/chamtool.py b/Software/chamtool.py index 8a1203f..1b8790b 100755 --- a/Software/chamtool.py +++ b/Software/chamtool.py @@ -182,6 +182,10 @@ def cmdUpgrade(chameleon, arg): print ("Device changed into Upgrade Mode") exit(0) +def cmdClear(chameleon, arg): + chameleon.cmdClear() + return "Slot has been cleared" + # Custom class for argparse class CmdListAction(argparse.Action): def __init__(self, option_strings, dest, default=False, required=False, @@ -224,6 +228,7 @@ def main(): cmdArgGroup.add_argument("-rl", "--rled", dest="rled", action=CmdListAction, metavar="FUNCTION", nargs='?', help="retrieve or set the current red led function") cmdArgGroup.add_argument("-th", "--threshold", dest="threshold", action=CmdListAction, nargs='?', help="retrieve or set the threshold") cmdArgGroup.add_argument("-ug", "--upgrade", dest="upgrade", action=CmdListAction, nargs=0, help="set the micro Controller to upgrade mode") + cmdArgGroup.add_argument("-cl", "--clear", dest="clear", action=CmdListAction, nargs=0, help="clear the slot") args = argParser.parse_args() @@ -258,6 +263,7 @@ def main(): "rled" : cmdRedLED, "threshold" : cmdThreshold, "upgrade" : cmdUpgrade, + "clear" : cmdClear, } if hasattr(args, "cmdList"):