#ifndef MFRC522_H #define MFRC522_H #include "../../Debug.h" #include "../../io/SoftSPI.h" /** * RFID reader based on MFRC522 * attached via SPI * * http://www.nxp.com/documents/data_sheet/MFRC522.pdf * * http://www.gorferay.com/initialization-and-anticollision-iso-iec-14433-3/ * https://community.nxp.com/thread/437908 * https://www.nxp.com/docs/en/application-note/AN10833.pdf * https://www.slideshare.net/contactsarbjeet/architecture-development-of-nfc-applications * http://www.gorferay.com/mifare-and-handling-of-uids/ * http://www.proxmark.org/files/Documents/13.56%20MHz%20-%20MIFARE%20Classic/MIFARE%20Classic%20clones/ISSI_IS23SC4439_User_Manual.pdf */ class MFRC522 { public: static constexpr const char* NAME = "MFRC522"; struct UID { uint8_t size = 0; // Number of bytes in the UID. 4, 7 or 10. uint8_t uidByte[10]; uint8_t sak; // The SAK (Select acknowledge) byte returned from the PICC after successful selection. }; private: enum class Register { COMMAND_REG = 0x01 << 1, INTERRUPTS_REG = 0x02 << 1, COM_IRQ_REG = 0x04 << 1, DIV_IRQ_REG = 0x05 << 1, ERROR_REG = 0x06 << 1, STATUS1_REG = 0x07 << 1, STATUS2_REG = 0x08 << 1, FIFO_DATA_REG = 0x09 << 1, // access fifo buffer FIFO_LEVEL_REG = 0x0A << 1, // number of available fifo data CONTROL_REG = 0x0C << 1, BIT_FRAMING_REG = 0x0D << 1, COLL_REG = 0x0E << 1, MODE_REG = 0x11 << 1, TX_MODE_REG = 0x12 << 1, RX_MODE_REG = 0x13 << 1, TX_CONTROL_REG = 0x14 << 1, TX_AUTO_REG = 0x15 << 1, DEMOG_REG = 0x19 << 1, CRC_RES_H = 0x21 << 1, CRC_RES_L = 0x22 << 1, MOD_WIDTH_REG = 0x24 << 1, T_MODE_REG = 0x2A << 1, T_PRESCALER_REG = 0x2B << 1, T_RELOAD_REG_H = 0x2C << 1, T_RELOAD_REG_L = 0x2D << 1, TEST_SEL_1_REG = 0x31 << 1, TEST_SEL_2_REG = 0x32 << 1, VERSION_REG = 0x37 << 1, // software version }; enum class Command { IDLE = 0x00, MEM = 0x01, GEN_RANDOM_ID = 0x02, CALC_CRC = 0x03, TRANSMIT = 0x04, NO_CMD_CHANGE = 0x07, RECEIVE = 0x08, TRANSCEIVE = 0x0C, MF_AUTHENT = 0x0E, SOFT_RESET = 0x0F, }; enum class PICCComand { REQA = 0x26, PICC_CMD_READ = 0x30, PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame. PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision. PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1 PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2 PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3 PICC_CMD_HLTA = 0x50, // HaLT command, Typ }; enum class Status { OK, TIMEOUT_IRQ, TIMEOUT_MANUAL, TIMEOUT_CRC, ERROR, RESPONSE_TOO_LONG, COLLISION, INVALID, INVALID_ATQA, INTERNAL_ERROR, WRONG_CRC, WRONG_BCC, }; /** convert status-code to status string */ const char* getStatusStr(const Status s) const; struct CommandReg { uint8_t command : 4; uint8_t power_down : 1; uint8_t receiver_off : 1; uint8_t reserved : 2; }; struct InterruptsReg { uint8_t timerInterruptEnable : 1; uint8_t errorInterruptEnable : 1; uint8_t loAlertInterruptEnable : 1; uint8_t hiAlertInterruptEnable : 1; uint8_t idleInterruptEnable : 1; uint8_t rxInterruptEnable : 1; uint8_t txInterruptEnable : 1; uint8_t inverted : 1; }; private: public: /** init */ void init(); /** check whether a RFID-reader is present or not */ bool isAvailable(); /** is there a card present? */ bool isCard(); /** yes, card is present -> read it */ bool select(UID* uidOut); /** read the given 14-byte sector from the card */ void read(uint8_t address); /** stop talking to the card */ void halt(); /** perform selftest */ void selftest(); private: void stopCrypto1() { clearRegBits(Register::STATUS2_REG, 0x08); // reset MFCrypto1On } void antennaOn(); Status requestA(uint8_t* bufferATQA, uint8_t* bufferSize) { return piccREQAorWUPA(PICCComand::REQA, bufferATQA, bufferSize); } Status piccREQAorWUPA(const PICCComand cmd, uint8_t* buffer, uint8_t* bufferSize); /** get card-type-name for the given 2-byte ATQA response */ const char* getCardType(const uint8_t* atqa) const; Status anticol(int level, UID* uid); Status select(int level, uint8_t* uidPart, UID* uid); /** * calculate the CRC for the given bytes and write them (2 bytes) to the given destination * NOTE: the CRC depends on the settings of ModeReg (bit 0 and 1) which control * the CRC-16's preset: 0x0000 0x6363 0xa671 0xffff * 0x6363 seems to be the "correct" one?! */ Status calculateCRC(const uint8_t *data, const uint8_t length, uint8_t* result); Status transceive(const uint8_t* srcData, const uint8_t srcDataLen, uint8_t* dstData, uint8_t* dstDataLen, uint8_t* validBits = nullptr, uint8_t rxAlign = 0, bool checkCRC = false) { debugMod1(NAME, "transceive %d bytes", srcDataLen); debugShow(srcData, srcDataLen); const Status s = picc(Command::TRANSCEIVE, 0x30, srcData, srcDataLen, dstData, dstDataLen, validBits, rxAlign, checkCRC); debugMod2(NAME, "transceive: %s, returned %d bytes", getStatusStr(s), *dstDataLen); debugShow(dstData, *dstDataLen); return s; } void fillFIFO(const uint8_t* srcData, const uint8_t srcDataLen) { setRegBits(Register::FIFO_LEVEL_REG, 0x80); // flush FIFO (empty any existing data) if (readReg8(Register::FIFO_LEVEL_REG) != 0) { debugMod(NAME, "!!!!!!!!!!!!!!!!! FIFO NOT EMPTY THOUGH IT SHOULD BE!\n"); } writeReg(Register::FIFO_DATA_REG, srcData, srcDataLen); // write to-be-sent data to FIFO if (readReg8(Register::FIFO_LEVEL_REG) != srcDataLen) { debugMod(NAME, "!!!!!!!!!!!!!!!!!failed to fill fifo\n"); } } void ensureSane() { setRegBits(Register::FIFO_LEVEL_REG, 0x80); // flush FIFO (empty any existing data) //writeReg8(Register::FIFO_LEVEL_REG, 0x80); // flush FIFO (empty any existing data) writeReg8(Register::COMMAND_REG, (int) Command::IDLE); // stop previous commands } Status picc(Command cmd, const uint8_t waitIRQ, const uint8_t* srcData, const uint16_t srcDataLen, uint8_t* dstData, uint8_t* dstLen, uint8_t* validBits, uint8_t rxAlign, bool checkCRC); private: void writeReg8(const Register reg, const Command cmd) { writeReg8(reg, (uint8_t) cmd); } void writeReg8(const Register reg, const uint8_t value) { spi::chipSelect(); writeTo((int)reg); spi::writeByte(value); spi::chipDeselect(); } void setRegBits(const Register reg, const uint8_t mask) { const uint8_t tmp = readReg8(reg); writeReg8(reg, tmp | mask); } void clearRegBits(const Register reg, const uint8_t mask) { const uint8_t tmp = readReg8(reg); writeReg8(reg, tmp & (~mask)); } void writeReg(const Register reg, const uint8_t* data, const uint16_t len) { spi::chipSelect(); writeTo((int)reg); for (int i = 0; i < len; ++i){ spi::writeByte(data[i]); } spi::chipDeselect(); } uint8_t readReg8(const Register reg) { spi::chipSelect(); readFrom((int)reg); const uint8_t res = spi::readByte(); spi::chipDeselect(); return res; } /** read multiple bytes from the same register (mainly FIFO) */ void readReg(const Register reg, uint8_t* dst, const uint8_t count, const uint8_t rxAlign = 0); // highest bit denotes whether reading or writing void readFrom(uint8_t address) { spi::writeByte(address | 0x80); } void writeTo(uint8_t address) { spi::writeByte(address); } }; void ICACHE_FLASH_ATTR MFRC522::init() { debugMod(NAME, "init"); spi::init(); //os_delay_us(5000); // Reset baud rates writeReg8(Register::TX_MODE_REG, 0x00); writeReg8(Register::RX_MODE_REG, 0x00); // Reset ModWidthReg writeReg8(Register::MOD_WIDTH_REG, 0x26); writeReg8(Register::T_MODE_REG, 0x80); writeReg8(Register::T_PRESCALER_REG, 0xA9); writeReg8(Register::T_RELOAD_REG_L, 0xE8); writeReg8(Register::T_RELOAD_REG_H, 0x03); writeReg8(Register::TX_AUTO_REG, 0x40); writeReg8(Register::MODE_REG, 0x3D); // very important. set CRC preset to 0x6363 by setting bit 0 and clearing bit 1 in MODE_REG antennaOn(); writeReg8(Register::COMMAND_REG, Command::IDLE); } void ICACHE_FLASH_ATTR MFRC522::readReg(const Register reg, uint8_t* dst, const uint8_t count, const uint8_t rxAlign) { spi::chipSelect(); // address for reading (MSB = 1) const uint8_t address = (int) reg | 0x80; spi::writeByte(address); int i; for (i = 0; i < count-1; ++i) { if ( (i == 0) && rxAlign ) { uint8_t mask = (0xFF << rxAlign) & 0xFF; const uint8_t value = spi::readWriteByte(address); dst[0] = (dst[0] & ~mask) | (value & mask); } else { dst[i] = spi::readWriteByte(address); } } // read last byte and stop reading dst[i] = spi::readWriteByte(0); spi::chipDeselect(); } MFRC522::Status ICACHE_FLASH_ATTR MFRC522::picc(Command cmd, const uint8_t waitIRQ, const uint8_t* srcData, const uint16_t srcDataLen, uint8_t* dstData, uint8_t* dstLen, uint8_t* validBits, uint8_t rxAlign, bool checkCRC) { const uint8_t txLastBits = validBits ? *validBits : 0; const uint8_t bitFraming = (rxAlign << 4) + txLastBits; //os_printf("-- txLastBits: %d, bitFraming: %d \n", txLastBits, bitFraming); ensureSane(); writeReg8(Register::COM_IRQ_REG, 0x7f); // clear interrupt flags fillFIFO(srcData, srcDataLen); writeReg8(Register::BIT_FRAMING_REG, bitFraming); writeReg8(Register::COMMAND_REG, (int) cmd); if (cmd == Command::TRANSCEIVE) { //os_printf("-- start seding...\n"); setRegBits(Register::BIT_FRAMING_REG, 0x80); // startSend = 1 } //const uint8_t _errReg = readReg8(Register::ERROR_REG); //os_printf("cur err reg: %d\n", (int)_errReg); int maxRuns = 10; while(true) { const uint8_t res = readReg8(Register::COM_IRQ_REG); //os_printf("irq reg: %d\n", res); // received the interrupt we are waiting for? if (res & waitIRQ) { //debugMod(NAME, "got needed interrupt -> done"); //os_printf("got needed interrupt\n"); break; } if (res & 0x01) { return Status::TIMEOUT_IRQ; } if (--maxRuns == 0) { //debugMod(NAME, "TIMEOUT"); //os_printf("picc() Timeout\n"); return Status::TIMEOUT_MANUAL; } os_delay_us(1000*5); } // buffer overflow? const uint8_t errReg = readReg8(Register::ERROR_REG); if (errReg & 0x13) {return Status::ERROR;} // caller wants to read a response? if (dstData && dstLen) { const uint8_t n = readReg8(Register::FIFO_LEVEL_REG); debugMod1(NAME, "got %d bytes to read", (int)n); //os_printf("got %d bytes to read\n", (int) n); // not enough space for the response data? if (n > *dstLen) { return Status::RESPONSE_TOO_LONG; } // read response *dstLen = n; readReg(Register::FIFO_DATA_REG, dstData, n, rxAlign); uint8_t _validBits = readReg8(Register::CONTROL_REG) & 0x07; if (validBits) { *validBits = _validBits; } #ifdef WITH_DEBUG // ensure fifo is now empty if (readReg8(Register::FIFO_LEVEL_REG) != 0) { debugMod(NAME, "!! FIFO is not empty though it should be!"); return Status::INTERNAL_ERROR; } #endif } if (errReg & 0x08) { return Status::COLLISION; } // TODO CRC return Status::OK; } MFRC522::Status ICACHE_FLASH_ATTR MFRC522::calculateCRC(const uint8_t *data, const uint8_t length, uint8_t* result) { debugMod1(NAME, "calculate CRC for %d bytes", length); writeReg8(Register::COMMAND_REG, Command::IDLE); // Stop any active command. writeReg8(Register::DIV_IRQ_REG, 0x04); // Clear the CRCIRq interrupt request bit fillFIFO(data, length); // fill the FIFO with the data to CRC writeReg8(Register::COMMAND_REG, Command::CALC_CRC); // Start the calculation // Wait for the CRC calculation to complete for (int i = 0; i < 25; ++i) { os_delay_us(1000*1); // wait for CRCIRq to be set const uint8_t n = readReg8(Register::DIV_IRQ_REG); // CRCIRq bit set - calculation done if (n & 0x04) { writeReg8(Register::COMMAND_REG, Command::IDLE); // Stop calculating CRC for new content in the FIFO. result[0] = readReg8(Register::CRC_RES_L); result[1] = readReg8(Register::CRC_RES_H); return Status::OK; } } debugMod(NAME, "CRC TIMEOUT"); return Status::TIMEOUT_CRC; } const char* ICACHE_FLASH_ATTR MFRC522::getCardType(const uint8_t* atqa) const { const uint16_t resp = atqa[0] << 8 | atqa[1]; if (resp == 0x0400) {return "MFOne-S50";} // http://www.elechouse.com/elechouse/images/product/13.56MHZ_RFID_Module/mifare_S50.pdf if (resp == 0x0200) {return "MFOne-S70";} if (resp == 0x4400) {return "MF-UltraLight";} if (resp == 0x0800) {return "MF-Pro";} if (resp == 0x4403) {return "MF Desire";} return "Unknown"; } MFRC522::Status ICACHE_FLASH_ATTR MFRC522::anticol(int level, UID* uid) { debugMod1(NAME, "anticol(%d)", level); // Prepare MFRC522 clearRegBits(Register::COLL_REG, 0x80); // ValuesAfterColl=1 => Bits received after collision are cleared. uint8_t out[9]; uint8_t res[9]; uint8_t resLen; Status s; // send 2 bytes: 0x93 0x20 and expect 5 bytes of response : 4 bytes UID + 1 byte BCC checksum // the first byte of the UID might be 0x88 to indicate that there are more requests needed // to get the full UID resLen = 5; out[0] = (uint8_t)PICCComand::PICC_CMD_SEL_CL1; out[1] = 0x20; writeReg8(Register::BIT_FRAMING_REG, 0); s = transceive(out, 2, res, &resLen); // transceive failed? if (s != Status::OK) {return s;} // response is SN0 SN1 SN2 SN3 BCC where BCC = SN0^SN1^SN2^SN3 -> check this! const uint8_t bcc = res[0] ^ res[1] ^ res[2] ^ res[3]; debugMod2(NAME, "check BCC: %02x == %02x ?", bcc, res[4]); // NOTES // if the received UID starts with SN0 = 0x88, then we have to read the next block (like UTF-8) // if the received UID starts with SN0 = 0x08, it is a random number that will change every time // adjust the UID-result accordingly if (res[0] != 0x88) {uid->uidByte[uid->size] = res[0]; ++uid->size;} {uid->uidByte[uid->size] = res[1]; ++uid->size;} {uid->uidByte[uid->size] = res[2]; ++uid->size;} {uid->uidByte[uid->size] = res[3]; ++uid->size;} // go on with select return select(level, res, uid); } MFRC522::Status ICACHE_FLASH_ATTR MFRC522::select(int level, uint8_t* uidPart, UID* uid) { debugMod1(NAME, "select(%d)", level); uint8_t cmd = (uint8_t) PICCComand::PICC_CMD_SEL_CL1; // TODO: depends on level uint8_t bcc = uidPart[0] ^ uidPart[1] ^ uidPart[2] ^ uidPart[3]; uint8_t out[9] = {cmd, 0x70, uidPart[0], uidPart[1], uidPart[2], uidPart[3], bcc, 0x00, 0x00}; // replace 0x00 0x00 with the crc calculateCRC(out, 7, &out[7]); // transmit uint8_t res[3]; uint8_t resLen = 3; Status status = transceive(out, 9, res, &resLen); // NOTES on SAK (selective ACK) // result starts with 0x04? -> UID not complete, go on reading! // result starts with 0x08? -> UID complete, // this one also contains some details on the card we see uint8_t SAK = res[0]; // another UID part needs to be read? (max: 3) if (SAK == 0x04) { return anticol(level+1, uid); } // done return status; } /** is there a card present? */ bool ICACHE_FLASH_ATTR MFRC522::isCard() { debugMod(NAME, "isCard()"); uint8_t bufferATQA[2] = {0x00, 0x00}; uint8_t bufferSize = sizeof(bufferATQA); // Reset baud rates writeReg8(Register::TX_MODE_REG, 0x00); writeReg8(Register::RX_MODE_REG, 0x00); // Reset ModWidthReg writeReg8(Register::MOD_WIDTH_REG, 0x26); // send REQA Status res = requestA(bufferATQA, &bufferSize); debugMod1(NAME, "isCard: %s", getStatusStr(res)); // card-type? if (res == Status::OK) { debugMod1(NAME, "card type: %s", getCardType(bufferATQA)); } // done return res == Status::OK; } /** yes, card is present -> read it */ bool ICACHE_FLASH_ATTR MFRC522::select(UID* uidOut) { debugMod(NAME, "select()"); ensureSane(); // reset UID uidOut->size = 0; Status res; res = anticol(1, uidOut); debugShow(uidOut->uidByte, 10); return (res == Status::OK); } /** read the given 14-byte sector from the card */ void ICACHE_FLASH_ATTR MFRC522::read(uint8_t address) { debugMod1(NAME, "read(%d)", address); os_delay_us(1000*50); clearRegBits(Register::COLL_REG, 0x80); writeReg8(Register::BIT_FRAMING_REG, 0); // READ command: 0x30 ADR CRC1 CRC2 uint8_t buffer[4] = {(uint8_t)PICCComand::PICC_CMD_READ, address}; // calculate the CRC calculateCRC(buffer, 2, &buffer[2]); // response-buffer uint8_t res[16]; uint8_t resLen = 16; // send transceive(buffer, 4, res, &resLen); } /** stop talking to the card */ void ICACHE_FLASH_ATTR MFRC522::halt() { debugMod(NAME, "halt()"); // HALT command: 0x50 ADR CRC1 CRC2 uint8_t buffer[4] = {(uint8_t)PICCComand::PICC_CMD_HLTA, 0x00}; // calculate the CRC calculateCRC(buffer, 2, &buffer[2]); // send uint8_t resLen = 1; transceive(buffer, 4, buffer, &resLen); } void ICACHE_FLASH_ATTR MFRC522::selftest() { //os_printf("MODE_REG: %d\n", readReg8(Register::MODE_REG)); //writeReg8(Register::MODE_REG, 61); //os_printf("MODE_REG: %d\n", readReg8(Register::MODE_REG)); uint8_t res[2]; // MUST BE 3c a2 uint8_t out2[9] = {0x93, 0x70, 0x12, 0x34, 0x56, 0x78, 0x08, 0x00, 0x00}; calculateCRC(out2, 7, res); debugShow(res, 2); } bool ICACHE_FLASH_ATTR MFRC522::isAvailable() { return readReg8(Register::MODE_REG) == 0x3D; } MFRC522::Status ICACHE_FLASH_ATTR MFRC522::piccREQAorWUPA(const PICCComand cmd, uint8_t* buffer, uint8_t* bufferSize) { clearRegBits(Register::COLL_REG, 0x80); uint8_t validBits = 7; const uint8_t _cmd = (uint8_t) cmd; const Status status = transceive(&_cmd, 1, buffer, bufferSize, &validBits); if (status != Status::OK) {return status;} //os_printf("response-size: %d, valid bits: %d\n", (int)*bufferSize, (int)validBits); // ATQA must be 16 bits if ((*bufferSize != 2) || (validBits != 0)) { return Status::INVALID_ATQA; } return Status::OK; } /** convert status-code to status string */ const char* ICACHE_FLASH_ATTR MFRC522::getStatusStr(const Status s) const { switch(s) { case Status::OK: return "OK"; case Status::TIMEOUT_IRQ: return "TIMEOUT (IRQ)"; case Status::TIMEOUT_MANUAL: return "TIMEOUT (manual)"; case Status::TIMEOUT_CRC: return "TIMEOUT (crc)"; case Status::ERROR: return "ERROR"; case Status::RESPONSE_TOO_LONG: return "RESPONSE TOO LONG"; case Status::COLLISION: return "COLLISION"; case Status::INVALID: return "INVALID"; case Status::INVALID_ATQA: return "INVALID_ATQA"; case Status::INTERNAL_ERROR: return "INTERNAL_ERROR"; case Status::WRONG_CRC: return "WRONG_CRC"; case Status::WRONG_BCC: return "WRONG_BCC"; default: return "UNKNOWN STATUS"; } } void ICACHE_FLASH_ATTR MFRC522::antennaOn() { uint8_t val = readReg8(Register::TX_CONTROL_REG); if ((val & 0x03) != 0x03) { writeReg8(Register::TX_CONTROL_REG, val | 0x03); } val = readReg8(Register::TX_CONTROL_REG); } #endif // MFRC522_H